From: Ian Jackson Date: Wed, 5 Aug 2009 12:15:57 +0000 (+0100) Subject: Found in /home/matthew/programming/irc/bot - live version, all files as found X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~matthewv/git?p=irc.git;a=commitdiff_plain;h=9a8c6f919c6e2df251c611630a1d4bdfb4205548;ds=sidebyside Found in /home/matthew/programming/irc/bot - live version, all files as found --- diff --git a/Servus-artichoke.py b/Servus-artichoke.py new file mode 100644 index 0000000..b737b89 --- /dev/null +++ b/Servus-artichoke.py @@ -0,0 +1,205 @@ +# This file is part of Acrobat. +# +# Acrobat is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; either version 2 of the License, +# or (at your option) any later version. +# +# Acrobat is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Acrobat; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA. + +# Andrew Walkingshaw +# Peter Corbett +# Matthew Vernon +# Stephen Early +# Richard Kettlewell 2: #we lost! +# c.gameq(bot,"pad "+game.trigger,bot.owner,conn,False,game) +# elif time.time()>game.losetime: #we randomly lost, take new trigger +# c.gameq(bot,cmd,bot.owner,conn,False,game) +# diff --git a/Servus-artichoke.py~ b/Servus-artichoke.py~ new file mode 100644 index 0000000..c57ee1b --- /dev/null +++ b/Servus-artichoke.py~ @@ -0,0 +1,205 @@ +# This file is part of Acrobat. +# +# Acrobat is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; either version 2 of the License, +# or (at your option) any later version. +# +# Acrobat is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Acrobat; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA. + +# Andrew Walkingshaw +# Peter Corbett +# Matthew Vernon +# Stephen Early +# Richard Kettlewell 2: #we lost! +# c.gameq(bot,"pad "+game.trigger,bot.owner,conn,False,game) +# elif time.time()>game.losetime: #we randomly lost, take new trigger +# c.gameq(bot,cmd,bot.owner,conn,False,game) +# diff --git a/Servus-chiark.py b/Servus-chiark.py new file mode 100644 index 0000000..128300a --- /dev/null +++ b/Servus-chiark.py @@ -0,0 +1,206 @@ +# This file is part of Acrobat. +# +# Acrobat is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; either version 2 of the License, +# or (at your option) any later version. +# +# Acrobat is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Acrobat; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA. + +# Andrew Walkingshaw +# Peter Corbett +# Matthew Vernon +# Stephen Early +# Richard Kettlewell 2: #we lost! +# c.gameq(bot,"pad "+game.trigger,bot.owner,conn,False,game) +# elif time.time()>game.losetime: #we randomly lost, take new trigger +# c.gameq(bot,cmd,bot.owner,conn,False,game) +# diff --git a/Servus-chiark.py~ b/Servus-chiark.py~ new file mode 100644 index 0000000..128300a --- /dev/null +++ b/Servus-chiark.py~ @@ -0,0 +1,206 @@ +# This file is part of Acrobat. +# +# Acrobat is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; either version 2 of the License, +# or (at your option) any later version. +# +# Acrobat is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Acrobat; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA. + +# Andrew Walkingshaw +# Peter Corbett +# Matthew Vernon +# Stephen Early +# Richard Kettlewell 2: #we lost! +# c.gameq(bot,"pad "+game.trigger,bot.owner,conn,False,game) +# elif time.time()>game.losetime: #we randomly lost, take new trigger +# c.gameq(bot,cmd,bot.owner,conn,False,game) +# diff --git a/acrobat.py b/acrobat.py index 59b9c86..6bed11a 100755 --- a/acrobat.py +++ b/acrobat.py @@ -9,6 +9,8 @@ # Contributors: # Peter Corbett # Matthew Vernon +# +# Stephen Early mostly deleted stuff # # This file is part of Acrobat. # @@ -31,39 +33,22 @@ Acrobat - an extensible, minmalist irc bot. """ -import string, urllib, sys, cPickle, os, random +import string, sys from ircbot import SingleServerIRCBot from irclib import nm_to_n, irc_lower -import config - -#splitting out the configuration to a separate (source, but this is incidental- -#it's just the nearest free parser) file. - -import config - -class Karma: - def __init__(self): - self.dict = {} class Acrobat(SingleServerIRCBot): - def __init__(self, channel, nickname, server, owner, port=6667): + def __init__(self,config): + server=config.server + port=config.port + nickname=config.nickname SingleServerIRCBot.__init__(self, [(server, port)], nickname, nickname) - self.channel = channel - self.owner = owner + self.channel = config.channel + self.owner = config.owner self.revision = "$Revision: 1.1 $" # global version number - self.trouts = config.trouts - self.karmafilename = config.karmafilename self.config = config - # load the karma db - try: - f = open(self.karmafilename, "r") - self.karma = cPickle.load(f) - f.close() - except IOError: - self.karma = Karma() - ## EVENT HANDLERS def on_welcome(self, conn, evt): @@ -77,80 +62,32 @@ class Acrobat(SingleServerIRCBot): nc = string.split(payload, " ", 1) if len(nc) > 1 and (irc_lower(nc[0]).startswith( irc_lower(self.connection.get_nickname()))): - self.do_command(self.channel, nc[1].strip(), public = 1) - elif payload[0] in config.triggers and len(payload)>1: - self.do_command(self.channel, payload[1:].strip(), public=1) - elif payload.find("++") != -1 or payload.find("--") != -1: - self.do_command(self.channel, payload.strip(), public=1) + self.do_command(nm_to_n(evt.source()), nc[1].strip(), public = 1) + elif len(payload)>1: + self.do_command(nm_to_n(evt.source()), payload.strip(), public = 1) # General query handler def do_command(self, nick, cmd, public=0): - conn = self.connection - command = cmd.split()[0] - sys.stderr.write(command) - args = (self, cmd, nick, conn, public) + self.config.command(self,cmd,nick,self.connection,public) - # regrettably, these (and anything else with special triggers) - # must be special-cased, which is aesthetically unsatisfying. - - # karma: up - if command.endswith("++"): - self.karmaup(cmd) - # karma: down - if command.endswith("--"): - self.karmadown(cmd) - - # and in the general case (this is slightly magical) - if command.lower() in config.commands.keys(): - config.commands[command](*args) - # else do nothing. - - # What this _means_ is: you write a - # function(bot, cmd, nick, conn, public), where bot is the bot class - # (ie self here), and drop it in commands.py; then add a trigger command - # to config.py for it, in the dictionary "commands", and it will - # just start working on bot restart or config reload. - - # This is, IMO, quite nifty. :) - - # And now the karma commands, as these pretty much have to be here :( - # increment karma - def karmaup(self, cmd): - if self.karma.dict.has_key(cmd.split()[0][:-2]): - self.karma.dict[cmd.split()[0][:-2]] += 1 - else: - self.karma.dict[cmd.split()[0][:-2]] = 1 - #decrement karma - def karmadown(self, cmd): - if self.karma.dict.has_key(cmd.split()[0][:-2]): - self.karma.dict[cmd.split()[0][:-2]] -= 1 - else: - self.karma.dict[cmd.split()[0][:-2]] = -1 - + # Convenience function - reply to a public message publicly, or + # a private message privately + def automsg(self,public,nick,msg): + if public: + self.connection.privmsg(self.channel,msg) + else: + self.connection.notice(nick, msg) def main(): - # initialize the bot - bot = Acrobat(config.channel, config.nickname, config.server, - config.owner, config.port) - sys.stderr.write("Trying to connect...\n") - # and the event loop + if len(sys.argv)!=2: + print "acrobat: provide configuration module name on command line" + sys.exit(1) + try: + c=__import__(sys.argv[1]) + except ImportError: + print "acrobat: configuration module "+sys.argv[1]+".py not found" + sys.exit(1) + bot = Acrobat(c) bot.start() -# if len(sys.argv) != 5: # insufficient arguments -# print "Usage: acrobat owner" -# sys.exit(1) -# sv_port = string.split(sys.argv[1], ":", 1) # tuple; (server, port) -# server = sv_port[0] -# if len(sv_port) == 2: -# try: -# port = int(sv_port[1]) -# except ValueError: -# print "Error: Erroneous port." -# sys.exit(1) -# else: -# port = 6667 # default irc port -# channel = sys.argv[2] -# nickname = sys.argv[3] -# owner = sys.argv[4] - if __name__ == "__main__": main() diff --git a/assassins.py b/assassins.py new file mode 100644 index 0000000..b6ed957 --- /dev/null +++ b/assassins.py @@ -0,0 +1,173 @@ +# This file is part of Acrobat. +# +# Acrobat is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; either version 2 of the License, +# or (at your option) any later version. +# +# Acrobat is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Acrobat; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA. + +# Andrew Walkingshaw +# Peter Corbett +# Matthew Vernon +# Stephen Early + +# Acrobat configuration file + +# The following definitions are required to be present in this module: +server = "kern.srcf.ucam.org" +port = 6667 +nickname = "Servus" +channel = "#assassins" +owner = "Atreic" +# Also a function called "command"; see later. + +# Everything else in this file is configuration-specific. + +# Most command implementations are stored in a separate module. +import commands as c + +# This fishpond is shared between trouts and flirts. It doesn't have to be; +# you can define as many ponds as you like. +class fish: + cur_fish=5 + max_fish=5 + nofish_time=60 + fish_time_inc=60 + fish_inc=2 + DoS=0 + Boring_Git='Nobody' + quotatime=0 + +# load a file full of flirts or trouts +def __load(filename): + try: + f = open(filename, "r") + r = [l.strip() for l in f.readlines() if l.find("%s") != -1] + f.close() + except IOError: + r = [ "doesn't know what to do about %s." ] + return r + +# (troutlist,selftroutmsg,DoSmsg,notargetmsg,nofishmsg,fishpond,selftroutprob) +troutcfg = ( + __load("trouts"), + ' (at the instigation of %s)', + "Sorry, but %s is being a spoilsport.", + "Who do you wish me to trout?", + "Fish stocks exhausted.", + fish, + 0.1) + +flirtcfg = ( + __load("flirts"), + ' (but %s is their secret admirer)', + "Sorry, but %s made me take Holy Orders.", + "Who do you wish me to flirt with?", + "My libido is over-used!", + fish, + 0.1) + +# Hacky command to output the current fishpond state +def fishq(bot, cmd, nick, conn, public,f): + from irclib import irc_lower + if not public and irc_lower(nick) == irc_lower(bot.owner): + state=("Fishpond state: cur_fish=%d, max_fish=%d, nofish_time=%d, " + +"fish_time_inc=%d, fish_inc=%d, DoS=%d, Boring_Git=%s, " + +"quotatime=%d")%(f.cur_fish,f.max_fish,f.nofish_time, + f.fish_time_inc,f.fish_inc,f.DoS,f.Boring_Git, + f.quotatime) + bot.automsg(public,nick,state) + +# Karma implementation +import cPickle +karmafilename = "karmadump.assassins" +# load the karma db +try: + f = open(karmafilename, "r") + karmadb = cPickle.load(f) + f.close() +except IOError: + karmadb = {} +# Modify karma +def karma(cmd, amount): + thing=cmd.split()[0][:-2].lower() + if karmadb.has_key(thing): + karmadb[thing] += amount + else: + karmadb[thing] = amount +def savekarma(): + try: + f = open(karmafilename, "w") + cPickle.dump(karmadb, f) + f.close() + except IOError: + sys.stderr.write("Problems dumping karma: probably lost :(") + +# When the bot exits we should save the karma db +def quit(bot,cmd,nick,conn,public): + savekarma() + c.quitq(bot,cmd,nick,conn,public) +def reload(bot,cmd,nick,conn,public): + savekarma() + c.reloadq(bot,cmd,nick,conn,public) + +# Command processing: whenever something is said that the bot can hear, +# "command" is invoked and must decide what to do. This configuration +# defines a couple of special cases (for karma) but is otherwise driven +# by a dictionary of commands. + +commands = {"karma": (c.karmaq,karmadb), + "karmalist": (c.listkeysq,karmadb), + "karmadel": (c.karmadelq,karmadb), + "info": (c.infoq,karmadb), + "trout": (c.troutq,troutcfg), + "fish": (fishq,fish), + "flirt": (c.troutq,flirtcfg), + "quiet": (c.nofishq,fish), + "reload": reload, + "quit": quit, + "die": quit, + "google": c.googleq, + "say": c.sayq, + "do": c.doq } +# disconnect and hop annoy people +# "disconnect": c.disconnq, +# "hop": c.disconnq } +commands["list"]=(c.listkeysq,commands) + +triggers = ("!", "~") # what character should the bot be invoked by: + # eg !trout, ~trout etc. + +def command(bot, cmd, nick, conn, public): + ours=0 + try: + if public and cmd[0] in triggers: + ours=1 + cmd=cmd[1:] + if not public: + ours=1 + command = cmd.split()[0] + except IndexError: + command="" + # karma: up + if command.endswith("++"): + karma(cmd,1) + # karma: down + if command.endswith("--"): + karma(cmd,-1) + + if ours and command.lower() in commands.keys(): + e=commands[command] + if callable(e): + e(bot,cmd,nick,conn,public) + else: + e[0](bot,cmd,nick,conn,public,*e[1:]) diff --git a/assassins.py~ b/assassins.py~ new file mode 100644 index 0000000..6e7678f --- /dev/null +++ b/assassins.py~ @@ -0,0 +1,173 @@ +# This file is part of Acrobat. +# +# Acrobat is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; either version 2 of the License, +# or (at your option) any later version. +# +# Acrobat is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Acrobat; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA. + +# Andrew Walkingshaw +# Peter Corbett +# Matthew Vernon +# Stephen Early + +# Acrobat configuration file + +# The following definitions are required to be present in this module: +server = "rapun" +port = 6667 +nickname = "Servus" +channel = "#chiark" +owner = "Emperor" +# Also a function called "command"; see later. + +# Everything else in this file is configuration-specific. + +# Most command implementations are stored in a separate module. +import commands as c + +# This fishpond is shared between trouts and flirts. It doesn't have to be; +# you can define as many ponds as you like. +class fish: + cur_fish=5 + max_fish=5 + nofish_time=60 + fish_time_inc=60 + fish_inc=2 + DoS=0 + Boring_Git='Nobody' + quotatime=0 + +# load a file full of flirts or trouts +def __load(filename): + try: + f = open(filename, "r") + r = [l.strip() for l in f.readlines() if l.find("%s") != -1] + f.close() + except IOError: + r = [ "doesn't know what to do about %s." ] + return r + +# (troutlist,selftroutmsg,DoSmsg,notargetmsg,nofishmsg,fishpond,selftroutprob) +troutcfg = ( + __load("trouts"), + ' (at the instigation of %s)', + "Sorry, but %s is being a spoilsport.", + "Who do you wish me to trout?", + "Fish stocks exhausted.", + fish, + 0.1) + +flirtcfg = ( + __load("flirts"), + ' (but %s is their secret admirer)', + "Sorry, but %s made me take Holy Orders.", + "Who do you wish me to flirt with?", + "My libido is over-used!", + fish, + 0.1) + +# Hacky command to output the current fishpond state +def fishq(bot, cmd, nick, conn, public,f): + from irclib import irc_lower + if not public and irc_lower(nick) == irc_lower(bot.owner): + state=("Fishpond state: cur_fish=%d, max_fish=%d, nofish_time=%d, " + +"fish_time_inc=%d, fish_inc=%d, DoS=%d, Boring_Git=%s, " + +"quotatime=%d")%(f.cur_fish,f.max_fish,f.nofish_time, + f.fish_time_inc,f.fish_inc,f.DoS,f.Boring_Git, + f.quotatime) + bot.automsg(public,nick,state) + +# Karma implementation +import cPickle +karmafilename = "karmadump" +# load the karma db +try: + f = open(karmafilename, "r") + karmadb = cPickle.load(f) + f.close() +except IOError: + karmadb = {} +# Modify karma +def karma(cmd, amount): + thing=cmd.split()[0][:-2].lower() + if karmadb.has_key(thing): + karmadb[thing] += amount + else: + karmadb[thing] = amount +def savekarma(): + try: + f = open(karmafilename, "w") + cPickle.dump(karmadb, f) + f.close() + except IOError: + sys.stderr.write("Problems dumping karma: probably lost :(") + +# When the bot exits we should save the karma db +def quit(bot,cmd,nick,conn,public): + savekarma() + c.quitq(bot,cmd,nick,conn,public) +def reload(bot,cmd,nick,conn,public): + savekarma() + c.reloadq(bot,cmd,nick,conn,public) + +# Command processing: whenever something is said that the bot can hear, +# "command" is invoked and must decide what to do. This configuration +# defines a couple of special cases (for karma) but is otherwise driven +# by a dictionary of commands. + +commands = {"karma": (c.karmaq,karmadb), + "karmalist": (c.listkeysq,karmadb), + "karmadel": (c.karmadelq,karmadb), + "info": (c.infoq,karmadb), + "trout": (c.troutq,troutcfg), + "fish": (fishq,fish), + "flirt": (c.troutq,flirtcfg), + "quiet": (c.nofishq,fish), + "reload": reload, + "quit": quit, + "die": quit, + "google": c.googleq, + "say": c.sayq, + "do": c.doq } +# disconnect and hop annoy people +# "disconnect": c.disconnq, +# "hop": c.disconnq } +commands["list"]=(c.listkeysq,commands) + +triggers = ("!", "~") # what character should the bot be invoked by: + # eg !trout, ~trout etc. + +def command(bot, cmd, nick, conn, public): + ours=0 + try: + if public and cmd[0] in triggers: + ours=1 + cmd=cmd[1:] + if not public: + ours=1 + command = cmd.split()[0] + except IndexError: + command="" + # karma: up + if command.endswith("++"): + karma(cmd,1) + # karma: down + if command.endswith("--"): + karma(cmd,-1) + + if ours and command.lower() in commands.keys(): + e=commands[command] + if callable(e): + e(bot,cmd,nick,conn,public) + else: + e[0](bot,cmd,nick,conn,public,*e[1:]) diff --git a/commands.py b/commands.py index 6f6d1f7..4bdc45c 100644 --- a/commands.py +++ b/commands.py @@ -1,98 +1,222 @@ # Part of Acrobat. -import string, cPickle, random, urllib, sys +import string, cPickle, random, urllib, sys, time, re, os from irclib import irc_lower, nm_to_n # query karma -def karmaq(bot, cmd, nick, conn, public): - # in public - if public == 1: - try: - if bot.karma.dict.has_key(cmd.split()[1]): - conn.privmsg(bot.channel, "%s has karma %s." - %(cmd.split()[1], - bot.karma.dict[cmd.split()[1]])) - else: - conn.privmsg(bot.channel, "%s has no karma set." % - cmd.split()[1]) - except IndexError: - conn.privmsg(bot.channel, "I have karma on %s items." % - len(bot.karma.dict.keys())) - # in private - else: - try: - if bot.karma.dict.has_key(cmd.split()[1]): - conn.notice(nick, "%s has karma %s." % - (cmd.split()[1], - bot.karma.dict[cmd.split()[1]])) - else: - conn.notice(nick, "I have karma on %s items." % - len(bot.karma.dict.keys())) - except IndexError: - conn.notice(nick, "I have karma on %s items." % - len(bot.karma.dict.keys())) -# query bot status -def infoq(bot, cmd, nick, conn, public): - if public == 1: - conn.privmsg(bot.channel, - "I am Acrobat %s, on %s, as nick %s." % - (bot.revision.split()[1], bot.channel, conn.get_nickname())) - conn.privmsg(bot.channel, - "My owner is %s; I have karma on %s items." % - (bot.owner, len(bot.karma.dict.keys()))) +def karmaq(bot, cmd, nick, conn, public, karma): + try: + item=cmd.split()[1].lower() + except IndexError: + item=None + if item==None: + bot.automsg(public,nick,"I have karma on %s items." % + len(karma.keys())) + elif karma.has_key(item): + bot.automsg(public,nick,"%s has karma %s." + %(item,karma[item])) else: - conn.notice(nick, "I am Acrobat %s, on %s, as nick %s." % - (bot.revision.split()[1], bot.channel, conn.get_nickname())) - conn.notice(nick, "My owner is %s; I have karma on %s items." % - (bot.owner, len(bot.karma.dict.keys()))) + bot.automsg(public,nick, "%s has no karma set." % item) -# trout someone -def troutq(bot, cmd, nick, conn, public): +# delete karma +def karmadelq(bot, cmd, nick, conn, public, karma): try: - target = string.join(cmd.split()[1:]) - me = bot.connection.get_nickname() - trout_msg = random.choice(bot.trouts) - # The bot won't trout itself; - if irc_lower(me) == irc_lower(target): - target = nick - conn.action(bot.channel, trout_msg % target) - if public == 0: - if random.random() <= bot.config.selftroutrisk: - conn.action(bot.channel, - "notes %s is conducting a whispering campaign." % nick) + item=cmd.split()[1].lower() except IndexError: - conn.notice(nick, "Who do you wish me to trout?") + conn.notice(nick, "What should I delete?") + return + if nick != bot.owner: + conn.notice(nick, "You are not my owner.") + return + if karma.has_key(item): + del karma[item] + conn.notice(nick, "Item %s deleted."%item) + else: + conn.notice(nick, "There is no karma stored for %s."%item) + +# help - provides the URL of the help file +def helpq(bot, cmd, nick, conn, public): + bot.automsg(public,nick, + "For help see http://www.pick.ucam.org/~matthew/irc/servus.html") + + +# query bot status +def infoq(bot, cmd, nick, conn, public, karma): + bot.automsg(public,nick, + ("I am Acrobat %s, on %s, as nick %s. "+ + "My owner is %s; I have karma on %s items.") % + (bot.revision.split()[1], bot.channel, conn.get_nickname(), + bot.owner, len(karma.keys()))) + +# Check on fish stocks +def fish_quota(pond): + if pond.DoS: + if time.time()>=pond.quotatime: + pond.DoS=0 + else: + return + if (time.time()-pond.quotatime)>pond.fish_time_inc: + pond.cur_fish+=(((time.time()-pond.quotatime) + /pond.fish_time_inc)*pond.fish_inc) + if pond.cur_fish>pond.max_fish: + pond.cur_fish=pond.max_fish + pond.quotatime=time.time() + +# trout someone, or flirt with them +def troutq(bot, cmd, nick, conn, public, cfg): + fishlist=cfg[0] + selftrout=cfg[1] + quietmsg=cfg[2] + notargetmsg=cfg[3] + nofishmsg=cfg[4] + fishpond=cfg[5] + selftroutchance=cfg[6] + + fish_quota(fishpond) + if fishpond.DoS: + conn.notice(nick, quietmsg%fishpond.Boring_Git) + return + if fishpond.cur_fish<=0: + conn.notice(nick, nofishmsg) + return + target = string.join(cmd.split()[1:]) + if len(target)==0: + conn.notice(nick, notargetmsg) + return + me = bot.connection.get_nickname() + trout_msg = random.choice(fishlist) + # The bot won't trout or flirt with itself; + if irc_lower(me) == irc_lower(target): + target = nick + # There's a chance the game may be given away if the request was not + # public... + if not public: + if random.random()<=selftroutchance: + trout_msg=trout_msg+(selftrout%nick) + + conn.action(bot.channel, trout_msg % target) + fishpond.cur_fish-=1 + +# slash a pair +def slashq(bot, cmd, nick, conn, public, cfg): + fishlist=cfg[0] + selfslash=cfg[1] + quietmsg=cfg[2] + notargetmsg=cfg[3] + nofishmsg=cfg[4] + fishpond=cfg[5] + selfslashchance=cfg[6] + + fish_quota(fishpond) + if fishpond.DoS: + conn.notice(nick, quietmsg%fishpond.Boring_Git) + return + if fishpond.cur_fish<=0: + conn.notice(nick, nofishmsg) + return + target = string.join(cmd.split()[1:]) + #who = cmd.split()[1:] + who = ' '.join(cmd.split()[1:]).split(' / ') + if len(who) < 2: + conn.notice(nick, "it takes two to tango!") + return + elif len(who) > 2: + conn.notice(nick, "we'll have none of that round here") + return + me = bot.connection.get_nickname() + slash_msg = random.choice(fishlist) + # The bot won't slash people with themselves + if irc_lower(who[0]) == irc_lower(who[1]): + conn.notice(nick, "oooooh no missus!") + return + # The bot won't slash with itself, instead slashing the requester + for n in [0,1]: + if irc_lower(me) == irc_lower(who[n]): + who[n] = nick + # Perhaps someone asked to slash themselves with the bot then we get + if irc_lower(who[0]) == irc_lower(who[1]): + conn.notice(nick, "you wish!") + return + # There's a chance the game may be given away if the request was not + # public... + if not public: + if random.random()<=selfslashchance: + slash_msg=slash_msg+(selfslash%nick) + + conn.action(bot.channel, slash_msg % (who[0], who[1])) + fishpond.cur_fish-=1 + +#query units +def unitq(bot, cmd, nick, conn, public): + args = ' '.join(cmd.split()[1:]).split(' as ') + if len(args) != 2: + args = ' '.join(cmd.split()[1:]).split(' / ') + if len(args) != 2: + conn.notice(nick, "syntax: units arg1 as arg2") + return + if args[1]=='?': + sin,sout=os.popen2(["units","--verbose",args[0]],"r") + else: + sin,sout=os.popen2(["units","--verbose",args[0],args[1]],"r") + sin.close() + res=sout.readlines() + #popen2 doesn't clean up the child properly. Do this by hand + child=os.wait() + if os.WEXITSTATUS(child[1])==0: + bot.automsg(public,nick,res[0].strip()) + else: + conn.notice(nick,'; '.join(map(lambda x: x.strip(),res))) + +# Shut up trouting for a minute +def nofishq(bot, cmd, nick, conn, public, fish): + fish.cur_fish=0 + fish.DoS=1 + fish.Boring_Git=nick + fish.quotatime=time.time() + fish.quotatime+=fish.nofish_time + conn.notice(nick, "Fish stocks depleted, as you wish.") # rehash bot config def reloadq(bot, cmd, nick, conn, public): - if irc_lower(nick) == irc_lower(bot.owner): + if not public and irc_lower(nick) == irc_lower(bot.owner): try: reload(bot.config) - bot.trouts = bot.config.trouts - conn.privmsg(nick, "Config reloaded.") + conn.notice(nick, "Config reloaded.") except ImportError: conn.notice(nick, "Config reloading failed!") else: - conn.notice(nick, "This command can only be invoked by my owner.") + bot.automsg(public,nick, + "Configuration can only be reloaded by my owner, by /msg.") + +# lose the game and/or install a new trigger word +def gameq(bot, cmd, nick, conn, public, game): + #only install a new trigger if it's not too short. + if len(' '.join(cmd.split()[1:]))>2: + game.trigger=' '.join(cmd.split()[1:]) + if (time.time()> game.grace): + if not public: + if irc_lower(nick) == irc_lower(bot.owner): + conn.action(bot.channel,"loses the game!") + else: + conn.privmsg(bot.channel,nick+" just lost the game!") + else: + if not public: + conn.notice(nick, "It's a grace period!") + game.grace=time.time()+60*20 #20 minutes' grace + game.losetime=time.time()+random.randrange(game.minlose,game.maxlose) + conn.notice(bot.owner, str(game.losetime-time.time())+" "+game.trigger) # quit irc def quitq(bot, cmd, nick, conn, public): if irc_lower(nick) == irc_lower(bot.owner): - try: - f = open(bot.karmafilename, "w") - cPickle.dump(bot.karma, f) - f.close() - except IOError: - sys.stderr.write("Problems dumping karma: probably lost :(") bot.die(msg = "I have been chosen!") - elif public == 1: - conn.privmsg(nick, "Such aggression in public!") + elif public: + conn.notice(nick, "Such aggression in public!") else: conn.notice(nick, "You're not my owner.") # google for something def googleq(bot, cmd, nick, conn, public): cmdrest = string.join(cmd.split()[1:]) - #sys.stderr.write(conn) # "I'm Feeling Lucky" rather than try and parse the html targ = ("http://www.google.com/search?q=%s&btnI=I'm+Feeling+Lucky" % urllib.quote_plus(cmdrest)) @@ -100,40 +224,69 @@ def googleq(bot, cmd, nick, conn, public): # get redirected and grab the resulting url for returning gsearch = urllib.urlopen(targ).geturl() if gsearch != targ: # we've found something - if public == 0: - conn.notice(nick, str(gsearch)) - else: # we haven't found anything. - conn.privmsg(nick, str(gsearch)) - else: - if public == 0: - conn.notice(nick, "No pages found.") - else: - conn.privmsg(nick, "No pages found.") + bot.automsg(public,nick,str(gsearch)) + else: # we haven't found anything. + bot.automsg(public,nick,"No pages found.") except IOError: # if the connection times out. This blocks. :( - if public == 0: - conn.notice(nick, "The web's broken. Waah!") + bot.automsg(public,nick,"The web's broken. Waah!") + +# Look up the definition of something using google +def defineq(bot, cmd, nick, conn, public): + cmdrest = string.join(cmd.split()[1:]) + targ = ("http://www.google.com/search?q=define%%3A%s&ie=utf-8&oe=utf-8" + % urllib.quote_plus(cmdrest)) + try: + # Just slurp everything into a string + defnpage = urllib.urlopen(targ).read() + # For definitions we really do have to parse the HTML, sadly. + # This is of course going to be a bit fragile. We first look for + # 'Definitions of %s on the Web' -- if this isn't present we + # assume we have the 'no definitions found page'. + # The first defn starts after the following

tag. + # Following that we assume that each definition is all the non-markup + # before a
tag. Currently we just dump out the first definition. + match = re.search(r"Definitions of .*? on the Web.*?

\s*([^>]*)
",defnpage,re.MULTILINE) + if match == None: + bot.automsg(public,nick,"Some things defy definition.") else: - conn.privmsg(nick, "The web's broken. Waah!") + # We assume google has truncated the definition for us so this + # won't flood the channel with text... + defn = " ".join(match.group(1).split("\n")); + bot.automsg(public,nick,defn) + except IOError: # if the connection times out. This blocks. :( + bot.automsg(public,nick,"The web's broken. Waah!") ### say to msg/channel def sayq(bot, cmd, nick, conn, public): if irc_lower(nick) == irc_lower(bot.owner): conn.privmsg(bot.channel, string.join(cmd.split()[1:])) else: - conn.privmsg(nick, "You're not my owner!") + if not public: + conn.notice(nick, "You're not my owner!") ### action to msg/channel def doq(bot, cmd, nick, conn, public): sys.stderr.write(irc_lower(bot.owner)) sys.stderr.write(irc_lower(nick)) - if public == 0: + if not public: if irc_lower(nick) == irc_lower(bot.owner): conn.action(bot.channel, string.join(cmd.split()[1:])) else: - conn.privmsg(nick, "You're not my owner!") + conn.notice(nick, "You're not my owner!") ###disconnect def disconnq(bot, cmd, nick, conn, public): if cmd == "disconnect": # hop off for 60s bot.disconnect(msg="Be right back.") +### list keys of a dictionary +def listkeysq(bot, cmd, nick, conn, public, dict): + bot.automsg(public,nick,string.join(dict.keys())) + +### rot13 text (yes, I could have typed out the letters....) +### also "foo".encode('rot13') would have worked +def rot13q(bot, cmd, nick, conn, public): + a=''.join(map(chr,range((ord('a')),(ord('z')+1)))) + b=a[13:]+a[:13] + trans=string.maketrans(a+a.upper(),b+b.upper()) + conn.notice(nick, string.join(cmd.split()[1:]).translate(trans)) diff --git a/commands.py.orig b/commands.py.orig new file mode 100644 index 0000000..026861e --- /dev/null +++ b/commands.py.orig @@ -0,0 +1,164 @@ +# Part of Acrobat. +import string, cPickle, random, urllib, sys, time +from irclib import irc_lower, nm_to_n + +# query karma +def karmaq(bot, cmd, nick, conn, public, karma): + try: + item=cmd.split()[1].lower() + except IndexError: + item=None + if item==None: + bot.automsg(public,nick,"I have karma on %s items." % + len(karma.keys())) + elif karma.has_key(item): + bot.automsg(public,nick,"%s has karma %s." + %(item,karma[item])) + else: + bot.automsg(public,nick, "%s has no karma set." % item) + +# delete karma +def karmadelq(bot, cmd, nick, conn, public, karma): + try: + item=cmd.split()[1].lower() + except IndexError: + conn.notice(nick, "What should I delete?") + return + if nick != bot.owner: + conn.notice(nick, "You are not my owner.") + return + if karma.has_key(item): + del karma[item] + conn.notice(nick, "Item %s deleted."%item) + else: + conn.notice(nick, "There is no karma stored for %s."%item) + +# query bot status +def infoq(bot, cmd, nick, conn, public, karma): + bot.automsg(public,nick, + ("I am Acrobat %s, on %s, as nick %s. "+ + "My owner is %s; I have karma on %s items.") % + (bot.revision.split()[1], bot.channel, conn.get_nickname(), + bot.owner, len(karma.keys()))) + +# Check on fish stocks +def fish_quota(pond): + if pond.DoS: + if time.time()>=pond.quotatime: + pond.DoS=0 + else: + return + if (time.time()-pond.quotatime)>pond.fish_time_inc: + pond.cur_fish+=(((time.time()-pond.quotatime) + /pond.fish_time_inc)*pond.fish_inc) + if pond.cur_fish>pond.max_fish: + pond.cur_fish=pond.max_fish + pond.quotatime=time.time() + +# trout someone, or flirt with them +def troutq(bot, cmd, nick, conn, public, cfg): + fishlist=cfg[0] + selftrout=cfg[1] + quietmsg=cfg[2] + notargetmsg=cfg[3] + nofishmsg=cfg[4] + fishpond=cfg[5] + selftroutchance=cfg[6] + + fish_quota(fishpond) + if fishpond.DoS: + conn.notice(nick, quietmsg%fishpond.Boring_Git) + return + if fishpond.cur_fish<=0: + conn.notice(nick, nofishmsg) + return + target = string.join(cmd.split()[1:]) + if len(target)==0: + conn.notice(nick, notargetmsg) + return + me = bot.connection.get_nickname() + trout_msg = random.choice(fishlist) + # The bot won't trout or flirt with itself; + if irc_lower(me) == irc_lower(target): + target = nick + # There's a chance the game may be given away if the request was not + # public... + if not public: + if random.random()<=selftroutchance: + trout_msg=trout_msg+(selftrout%nick) + + conn.action(bot.channel, trout_msg % target) + fishpond.cur_fish-=1 + +# Shut up trouting for a minute +def nofishq(bot, cmd, nick, conn, public, fish): + fish.cur_fish=0 + fish.DoS=1 + fish.Boring_Git=nick + fish.quotatime=time.time() + fish.quotatime+=fish.nofish_time + conn.notice(nick, "Fish stocks depleted, as you wish.") + +# rehash bot config +def reloadq(bot, cmd, nick, conn, public): + if not public and irc_lower(nick) == irc_lower(bot.owner): + try: + reload(bot.config) + conn.notice(nick, "Config reloaded.") + except ImportError: + conn.notice(nick, "Config reloading failed!") + else: + bot.automsg(public,nick, + "Configuration can only be reloaded by my owner, by /msg.") + +# quit irc +def quitq(bot, cmd, nick, conn, public): + if irc_lower(nick) == irc_lower(bot.owner): + bot.die(msg = "I have been chosen!") + elif public: + conn.notice(nick, "Such aggression in public!") + else: + conn.notice(nick, "You're not my owner.") + +# google for something +def googleq(bot, cmd, nick, conn, public): + cmdrest = string.join(cmd.split()[1:]) + # "I'm Feeling Lucky" rather than try and parse the html + targ = ("http://www.google.com/search?q=%s&btnI=I'm+Feeling+Lucky" + % urllib.quote_plus(cmdrest)) + try: + # get redirected and grab the resulting url for returning + gsearch = urllib.urlopen(targ).geturl() + if gsearch != targ: # we've found something + bot.automsg(public,nick,str(gsearch)) + else: # we haven't found anything. + bot.automsg(public,nick,"No pages found.") + except IOError: # if the connection times out. This blocks. :( + bot.automsg(public,nick,"The web's broken. Waah!") + +### say to msg/channel +def sayq(bot, cmd, nick, conn, public): + if irc_lower(nick) == irc_lower(bot.owner): + conn.privmsg(bot.channel, string.join(cmd.split()[1:])) + else: + if not public: + conn.notice(nick, "You're not my owner!") + +### action to msg/channel +def doq(bot, cmd, nick, conn, public): + sys.stderr.write(irc_lower(bot.owner)) + sys.stderr.write(irc_lower(nick)) + if not public: + if irc_lower(nick) == irc_lower(bot.owner): + conn.action(bot.channel, string.join(cmd.split()[1:])) + else: + conn.notice(nick, "You're not my owner!") + +###disconnect +def disconnq(bot, cmd, nick, conn, public): + if cmd == "disconnect": # hop off for 60s + bot.disconnect(msg="Be right back.") + +### list keys of a dictionary +def listkeysq(bot, cmd, nick, conn, public, dict): + bot.automsg(public,nick,string.join(dict.keys())) diff --git a/commands.py.rej b/commands.py.rej new file mode 100644 index 0000000..271ff08 --- /dev/null +++ b/commands.py.rej @@ -0,0 +1,42 @@ +*************** +*** 133,138 **** + bot.automsg(public,nick,str(gsearch)) + else: # we haven't found anything. + bot.automsg(public,nick,"No pages found.") + except IOError: # if the connection times out. This blocks. :( + bot.automsg(public,nick,"The web's broken. Waah!") + +--- 133,165 ---- + bot.automsg(public,nick,str(gsearch)) + else: # we haven't found anything. + bot.automsg(public,nick,"No pages found.") ++ except IOError: # if the connection times out. This blocks. :( ++ bot.automsg(public,nick,"The web's broken. Waah!") ++ ++ # Look up the definition of something using google ++ def defineq(bot, cmd, nick, conn, public): ++ cmdrest = string.join(cmd.split()[1:]) ++ targ = ("http://www.google.com/search?q=define%%3A%s&ie=utf-8&oe=utf-8" ++ % urllib.quote_plus(cmdrest)) ++ try: ++ # Just slurp everything into a string ++ defnpage = urllib.urlopen(targ).read() ++ # For definitions we really do have to parse the HTML, sadly. ++ # This is of course going to be a bit fragile. We first look for ++ # 'Definitions of %s on the Web' -- if this isn't present we ++ # assume we have the 'no definitions found page'. ++ # The first defn starts after the following

tag. ++ # Following that we assume that each definition is all the non-markup ++ # before a
tag. Currently we just dump out the first definition. ++ match = re.search(r"Definitions of .*? on the Web.*?

\s*([^>]*)
",defnpage,re.MULTILINE) ++ if match == None: ++ bot.automsg(public,nick,"Some things defy definition.") ++ else: ++ # We assume google has truncated the definition for us so this ++ # won't flood the channel with text... ++ defn = " ".join(match.group(1).split("\n")); ++ bot.automsg(public,nick,defn) ++ + except IOError: # if the connection times out. This blocks. :( + bot.automsg(public,nick,"The web's broken. Waah!") + diff --git a/commands.py~ b/commands.py~ new file mode 100644 index 0000000..2bf2a98 --- /dev/null +++ b/commands.py~ @@ -0,0 +1,291 @@ +# Part of Acrobat. +import string, cPickle, random, urllib, sys, time, re, os +from irclib import irc_lower, nm_to_n + +# query karma +def karmaq(bot, cmd, nick, conn, public, karma): + try: + item=cmd.split()[1].lower() + except IndexError: + item=None + if item==None: + bot.automsg(public,nick,"I have karma on %s items." % + len(karma.keys())) + elif karma.has_key(item): + bot.automsg(public,nick,"%s has karma %s." + %(item,karma[item])) + else: + bot.automsg(public,nick, "%s has no karma set." % item) + +# delete karma +def karmadelq(bot, cmd, nick, conn, public, karma): + try: + item=cmd.split()[1].lower() + except IndexError: + conn.notice(nick, "What should I delete?") + return + if nick != bot.owner: + conn.notice(nick, "You are not my owner.") + return + if karma.has_key(item): + del karma[item] + conn.notice(nick, "Item %s deleted."%item) + else: + conn.notice(nick, "There is no karma stored for %s."%item) + +# help - provides the URL of the help file +def helpq(bot, cmd, nick, conn, public): + bot.automsg(public,nick, + "For help see http://www.pick.ucam.org/~matthew/irc/servus.html") + + +# query bot status +def infoq(bot, cmd, nick, conn, public, karma): + bot.automsg(public,nick, + ("I am Acrobat %s, on %s, as nick %s. "+ + "My owner is %s; I have karma on %s items.") % + (bot.revision.split()[1], bot.channel, conn.get_nickname(), + bot.owner, len(karma.keys()))) + +# Check on fish stocks +def fish_quota(pond): + if pond.DoS: + if time.time()>=pond.quotatime: + pond.DoS=0 + else: + return + if (time.time()-pond.quotatime)>pond.fish_time_inc: + pond.cur_fish+=(((time.time()-pond.quotatime) + /pond.fish_time_inc)*pond.fish_inc) + if pond.cur_fish>pond.max_fish: + pond.cur_fish=pond.max_fish + pond.quotatime=time.time() + +# trout someone, or flirt with them +def troutq(bot, cmd, nick, conn, public, cfg): + fishlist=cfg[0] + selftrout=cfg[1] + quietmsg=cfg[2] + notargetmsg=cfg[3] + nofishmsg=cfg[4] + fishpond=cfg[5] + selftroutchance=cfg[6] + + fish_quota(fishpond) + if fishpond.DoS: + conn.notice(nick, quietmsg%fishpond.Boring_Git) + return + if fishpond.cur_fish<=0: + conn.notice(nick, nofishmsg) + return + target = string.join(cmd.split()[1:]) + if len(target)==0: + conn.notice(nick, notargetmsg) + return + me = bot.connection.get_nickname() + trout_msg = random.choice(fishlist) + # The bot won't trout or flirt with itself; + if irc_lower(me) == irc_lower(target): + target = nick + # There's a chance the game may be given away if the request was not + # public... + if not public: + if random.random()<=selftroutchance: + trout_msg=trout_msg+(selftrout%nick) + + conn.action(bot.channel, trout_msg % target) + fishpond.cur_fish-=1 + +# slash a pair +def slashq(bot, cmd, nick, conn, public, cfg): + fishlist=cfg[0] + selfslash=cfg[1] + quietmsg=cfg[2] + notargetmsg=cfg[3] + nofishmsg=cfg[4] + fishpond=cfg[5] + selfslashchance=cfg[6] + + fish_quota(fishpond) + if fishpond.DoS: + conn.notice(nick, quietmsg%fishpond.Boring_Git) + return + if fishpond.cur_fish<=0: + conn.notice(nick, nofishmsg) + return + target = string.join(cmd.split()[1:]) + #who = cmd.split()[1:] + who = ' '.join(cmd.split()[1:]).split(' / ') + if len(who) < 2: + conn.notice(nick, "it takes two to tango!") + return + elif len(who) > 2: + conn.notice(nick, "we'll have none of that round here") + return + me = bot.connection.get_nickname() + slash_msg = random.choice(fishlist) + # The bot won't slash people with themselves + if irc_lower(who[0]) == irc_lower(who[1]): + conn.notice(nick, "oooooh no missus!") + return + # The bot won't slash with itself, instead slashing the requester + for n in [0,1]: + if irc_lower(me) == irc_lower(who[n]): + who[n] = nick + # Perhaps someone asked to slash themselves with the bot then we get + if irc_lower(who[0]) == irc_lower(who[1]): + conn.notice(nick, "you wish!") + return + # There's a chance the game may be given away if the request was not + # public... + if not public: + if random.random()<=selfslashchance: + slash_msg=slash_msg+(selfslash%nick) + + conn.action(bot.channel, slash_msg % (who[0], who[1])) + fishpond.cur_fish-=1 + +#query units +def unitq(bot, cmd, nick, conn, public): + args = ' '.join(cmd.split()[1:]).split(' as ') + if len(args) != 2: + args = ' '.join(cmd.split()[1:]).split(' / ') + if len(args) != 2: + conn.notice(nick, "syntax: units arg1 as arg2") + return + if args[1]=='?': + sin,sout=os.popen2(["units","--verbose",args[0]],"r") + else: + sin,sout=os.popen2(["units","--verbose",args[0],args[1]],"r") + sin.close() + res=sout.readlines() + #popen2 doesn't clean up the child properly. Do this by hand + child=os.wait() + if os.WEXITSTATUS(child[1])==0: + bot.automsg(public,nick,res[0].strip()) + else: + conn.notice(nick,'; '.join(map(lambda x: x.strip(),res))) + +# Shut up trouting for a minute +def nofishq(bot, cmd, nick, conn, public, fish): + fish.cur_fish=0 + fish.DoS=1 + fish.Boring_Git=nick + fish.quotatime=time.time() + fish.quotatime+=fish.nofish_time + conn.notice(nick, "Fish stocks depleted, as you wish.") + +# rehash bot config +def reloadq(bot, cmd, nick, conn, public): + if not public and irc_lower(nick) == irc_lower(bot.owner): + try: + reload(bot.config) + conn.notice(nick, "Config reloaded.") + except ImportError: + conn.notice(nick, "Config reloading failed!") + else: + bot.automsg(public,nick, + "Configuration can only be reloaded by my owner, by /msg.") + +# lose the game and/or install a new trigger word +def gameq(bot, cmd, nick, conn, public, game): + #only install a new trigger if it's not too short. + if len(' '.join(cmd.split()[1:]))>2: + game.trigger=' '.join(cmd.split()[1:]) + if (time.time()> game.grace): + if not public: + if irc_lower(nick) == irc_lower(bot.owner): + conn.action(bot.channel,"loses the game!") + else: + conn.privmsg(bot.channel,nick+" just lost the game!") + else: + if not public: + conn.notice(nick, "It's a grace period!") + game.grace=time.time()+60*20 #20 minutes' grace + game.losetime=time.time()+random.randrange(game.minlose,game.maxlose) + conn.notice(bot.owner, str(game.losetime-time.time())+" "+game.trigger) + +# quit irc +def quitq(bot, cmd, nick, conn, public): + if irc_lower(nick) == irc_lower(bot.owner): + bot.die(msg = "I have been chosen!") + elif public: + conn.notice(nick, "Such aggression in public!") + else: + conn.notice(nick, "You're not my owner.") + +# google for something +def googleq(bot, cmd, nick, conn, public): + cmdrest = string.join(cmd.split()[1:]) + # "I'm Feeling Lucky" rather than try and parse the html + targ = ("http://www.google.com/search?q=%s&btnI=I'm+Feeling+Lucky" + % urllib.quote_plus(cmdrest)) + try: + # get redirected and grab the resulting url for returning + gsearch = urllib.urlopen(targ).geturl() + if gsearch != targ: # we've found something + bot.automsg(public,nick,str(gsearch)) + else: # we haven't found anything. + bot.automsg(public,nick,"No pages found.") + except IOError: # if the connection times out. This blocks. :( + bot.automsg(public,nick,"The web's broken. Waah!") + +# Look up the definition of something using google +def defineq(bot, cmd, nick, conn, public): + cmdrest = string.join(cmd.split()[1:]) + targ = ("http://www.google.com/search?q=define%%3A%s&ie=utf-8&oe=utf-8" + % urllib.quote_plus(cmdrest)) + try: + # Just slurp everything into a string + defnpage = urllib.urlopen(targ).read() + # For definitions we really do have to parse the HTML, sadly. + # This is of course going to be a bit fragile. We first look for + # 'Definitions of %s on the Web' -- if this isn't present we + # assume we have the 'no definitions found page'. + # The first defn starts after the following

tag. + # Following that we assume that each definition is all the non-markup + # before a
tag. Currently we just dump out the first definition. + match = re.search(r"Definitions of .*? on the Web.*?

\s*([^>]*)
",defnpage,re.MULTILINE) + if match == None: + bot.automsg(public,nick,"Some things defy definition.") + else: + # We assume google has truncated the definition for us so this + # won't flood the channel with text... + defn = " ".join(match.group(1).split("\n")); + bot.automsg(public,nick,defn) + except IOError: # if the connection times out. This blocks. :( + bot.automsg(public,nick,"The web's broken. Waah!") + +### say to msg/channel +def sayq(bot, cmd, nick, conn, public): + if irc_lower(nick) == irc_lower(bot.owner): + conn.privmsg(bot.channel, string.join(cmd.split()[1:])) + else: + if not public: + conn.notice(nick, "You're not my owner!") + +### action to msg/channel +def doq(bot, cmd, nick, conn, public): + sys.stderr.write(irc_lower(bot.owner)) + sys.stderr.write(irc_lower(nick)) + if not public: + if irc_lower(nick) == irc_lower(bot.owner): + conn.action(bot.channel, string.join(cmd.split()[1:])) + else: + conn.notice(nick, "You're not my owner!") + +###disconnect +def disconnq(bot, cmd, nick, conn, public): + if cmd == "disconnect": # hop off for 60s + bot.disconnect(msg="Be right back.") + +### list keys of a dictionary +def listkeysq(bot, cmd, nick, conn, public, dict): + bot.automsg(public,nick,string.join(dict.keys())) + +### rot13 text (yes, I could have typed out the letters....) +def rot13q(bot, cmd, nick, conn, public): + a=''.join(map(chr,range((ord('a')),(ord('z')+1)))) + b=a[13:]+a[:13] + trans=string.maketrans(a+a.upper(),b+b.upper()) + conn.notice(nick, string.join(cmd.split()[1:]).translate(trans)) diff --git a/config.py b/config.py index df074e0..1f989c6 100644 --- a/config.py +++ b/config.py @@ -18,42 +18,156 @@ # Andrew Walkingshaw # Peter Corbett # Matthew Vernon +# Stephen Early # Acrobat configuration file -# this is just python source, so has to be formatted as such -from commands import * -karmafilename = "karmadump" +# The following definitions are required to be present in this module: +server = "rapun" +port = 6667 +nickname = "Testbot" +channel = "#test" +owner = "Emperor" +# Also a function called "command"; see later. -trouts = ("questions %s's parentage.", - "slaps %s about a bit with a wet trout", - "thumbs its nose at %s.") +# Everything else in this file is configuration-specific. -selftroutrisk = 0.1 # some number between 0 and 1... - # the risk of a trout rebounding on the trouter! - -server = "irc.barrysworld.com" +# Most command implementations are stored in a separate module. +import commands as c -port = 6667 +# This fishpond is shared between trouts and flirts. It doesn't have to be; +# you can define as many ponds as you like. +class fish: + cur_fish=5 + max_fish=5 + nofish_time=60 + fish_time_inc=60 + fish_inc=2 + DoS=0 + Boring_Git='Nobody' + quotatime=0 -nickname = "Acrobat" +# load a file full of flirts or trouts +def __load(filename): + try: + f = open(filename, "r") + r = [l.strip() for l in f.readlines() if l.find("%s") != -1] + f.close() + except IOError: + r = [ "doesn't know what to do about %s." ] + return r -channel = "#acrotest" +# (troutlist,selftroutmsg,DoSmsg,notargetmsg,nofishmsg,fishpond,selftroutprob) +troutcfg = ( + __load("trouts"), + ' (at the instigation of %s)', + "Sorry, but %s is being a spoilsport.", + "Who do you wish me to trout?", + "Fish stocks exhausted.", + fish, + 0.1) -owner = "Acronym" +flirtcfg = ( + __load("flirts"), + ' (but %s is their secret admirer)', + "Sorry, but %s made me take Holy Orders.", + "Who do you wish me to flirt with?", + "My libido is over-used!", + fish, + 0.1) + +# Hacky command to output the current fishpond state +def fishq(bot, cmd, nick, conn, public,f): + from irclib import irc_lower + if not public and irc_lower(nick) == irc_lower(bot.owner): + state=("Fishpond state: cur_fish=%d, max_fish=%d, nofish_time=%d, " + +"fish_time_inc=%d, fish_inc=%d, DoS=%d, Boring_Git=%s, " + +"quotatime=%d")%(f.cur_fish,f.max_fish,f.nofish_time, + f.fish_time_inc,f.fish_inc,f.DoS,f.Boring_Git, + f.quotatime) + bot.automsg(public,nick,state) + +# Karma implementation +import cPickle +karmafilename = "karmadump" +# load the karma db +try: + f = open(karmafilename, "r") + karmadb = cPickle.load(f) + f.close() +except IOError: + karmadb = {} +# Modify karma +def karma(cmd, amount): + thing=cmd.split()[0][:-2].lower() + if karmadb.has_key(thing): + karmadb[thing] += amount + else: + karmadb[thing] = amount +def savekarma(): + try: + f = open(karmafilename, "w") + cPickle.dump(karmadb, f) + f.close() + except IOError: + sys.stderr.write("Problems dumping karma: probably lost :(") + +# When the bot exits we should save the karma db +def quit(bot,cmd,nick,conn,public): + savekarma() + c.quitq(bot,cmd,nick,conn,public) +def reload(bot,cmd,nick,conn,public): + savekarma() + c.reloadq(bot,cmd,nick,conn,public) + +# Command processing: whenever something is said that the bot can hear, +# "command" is invoked and must decide what to do. This configuration +# defines a couple of special cases (for karma) but is otherwise driven +# by a dictionary of commands. + +commands = {"karma": (c.karmaq,karmadb), + "karmalist": (c.listkeysq,karmadb), + "karmadel": (c.karmadelq,karmadb), + "info": (c.infoq,karmadb), + "trout": (c.troutq,troutcfg), + "flirt": (c.troutq,flirtcfg), + "fish": (fishq,fish), + "quiet": (c.nofishq,fish), + "reload": reload, + "quit": quit, + "google": c.googleq, + "define": c.defineq, + "say": c.sayq, + "do": c.doq } +# disconnect and hop annoy people +# "disconnect": c.disconnq, +# "hop": c.disconnq } +commands["list"]=(c.listkeysq,commands) triggers = ("!", "~") # what character should the bot be invoked by: # eg !trout, ~trout etc. -# these are "command": (function in commands.py) pairs. - -commands = {"karma": karmaq, - "info": infoq, - "trout": troutq, - "reload": reloadq, - "quit": quitq, - "google": googleq, - "say": sayq, - "do": doq, - "disconnect": disconnq, - "hop": disconnq } +def command(bot, cmd, nick, conn, public): + ours=0 + try: + if public and cmd[0] in triggers: + ours=1 + cmd=cmd[1:] + if not public: + ours=1 + command = cmd.split()[0] + except IndexError: + command="" + # karma: up + if command.endswith("++"): + karma(cmd,1) + # karma: down + if command.endswith("--"): + karma(cmd,-1) + + if ours and command.lower() in commands.keys(): + e=commands[command] + if callable(e): + e(bot,cmd,nick,conn,public) + else: + e[0](bot,cmd,nick,conn,public,*e[1:]) diff --git a/config.py~ b/config.py~ new file mode 100644 index 0000000..5701288 --- /dev/null +++ b/config.py~ @@ -0,0 +1,172 @@ +# This file is part of Acrobat. +# +# Acrobat is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; either version 2 of the License, +# or (at your option) any later version. +# +# Acrobat is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Acrobat; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA. + +# Andrew Walkingshaw +# Peter Corbett +# Matthew Vernon +# Stephen Early + +# Acrobat configuration file + +# The following definitions are required to be present in this module: +server = "rapun" +port = 6667 +nickname = "Testbot" +channel = "#test" +owner = "Emperor" +# Also a function called "command"; see later. + +# Everything else in this file is configuration-specific. + +# Most command implementations are stored in a separate module. +import commands as c + +# This fishpond is shared between trouts and flirts. It doesn't have to be; +# you can define as many ponds as you like. +class fish: + cur_fish=5 + max_fish=5 + nofish_time=60 + fish_time_inc=60 + fish_inc=2 + DoS=0 + Boring_Git='Nobody' + quotatime=0 + +# load a file full of flirts or trouts +def __load(filename): + try: + f = open(filename, "r") + r = [l.strip() for l in f.readlines() if l.find("%s") != -1] + f.close() + except IOError: + r = [ "doesn't know what to do about %s." ] + return r + +# (troutlist,selftroutmsg,DoSmsg,notargetmsg,nofishmsg,fishpond,selftroutprob) +troutcfg = ( + __load("trouts"), + ' (at the instigation of %s)', + "Sorry, but %s is being a spoilsport.", + "Who do you wish me to trout?", + "Fish stocks exhausted.", + fish, + 0.1) + +flirtcfg = ( + __load("flirts"), + ' (but %s is their secret admirer)', + "Sorry, but %s made me take Holy Orders.", + "Who do you wish me to flirt with?", + "My libido is over-used!", + fish, + 0.1) + +# Hacky command to output the current fishpond state +def fishq(bot, cmd, nick, conn, public,f): + from irclib import irc_lower + if not public and irc_lower(nick) == irc_lower(bot.owner): + state=("Fishpond state: cur_fish=%d, max_fish=%d, nofish_time=%d, " + +"fish_time_inc=%d, fish_inc=%d, DoS=%d, Boring_Git=%s, " + +"quotatime=%d")%(f.cur_fish,f.max_fish,f.nofish_time, + f.fish_time_inc,f.fish_inc,f.DoS,f.Boring_Git, + f.quotatime) + bot.automsg(public,nick,state) + +# Karma implementation +import cPickle +karmafilename = "karmadump" +# load the karma db +try: + f = open(karmafilename, "r") + karmadb = cPickle.load(f) + f.close() +except IOError: + karmadb = {} +# Modify karma +def karma(cmd, amount): + thing=cmd.split()[0][:-2].lower() + if karmadb.has_key(thing): + karmadb[thing] += amount + else: + karmadb[thing] = amount +def savekarma(): + try: + f = open(karmafilename, "w") + cPickle.dump(karmadb, f) + f.close() + except IOError: + sys.stderr.write("Problems dumping karma: probably lost :(") + +# When the bot exits we should save the karma db +def quit(bot,cmd,nick,conn,public): + savekarma() + c.quitq(bot,cmd,nick,conn,public) +def reload(bot,cmd,nick,conn,public): + savekarma() + c.reloadq(bot,cmd,nick,conn,public) + +# Command processing: whenever something is said that the bot can hear, +# "command" is invoked and must decide what to do. This configuration +# defines a couple of special cases (for karma) but is otherwise driven +# by a dictionary of commands. + +commands = {"karma": (c.karmaq,karmadb), + "karmalist": (c.listkeysq,karmadb), + "karmadel": (c.karmadelq,karmadb), + "info": (c.infoq,karmadb), + "trout": (c.troutq,troutcfg), + "flirt": (c.troutq,flirtcfg), + "fish": (fishq,fish), + "quiet": (c.nofishq,fish), + "reload": reload, + "quit": quit, + "google": c.googleq, + "say": c.sayq, + "do": c.doq } +# disconnect and hop annoy people +# "disconnect": c.disconnq, +# "hop": c.disconnq } +commands["list"]=(c.listkeysq,commands) + +triggers = ("!", "~") # what character should the bot be invoked by: + # eg !trout, ~trout etc. + +def command(bot, cmd, nick, conn, public): + ours=0 + try: + if public and cmd[0] in triggers: + ours=1 + cmd=cmd[1:] + if not public: + ours=1 + command = cmd.split()[0] + except IndexError: + command="" + # karma: up + if command.endswith("++"): + karma(cmd,1) + # karma: down + if command.endswith("--"): + karma(cmd,-1) + + if ours and command.lower() in commands.keys(): + e=commands[command] + if callable(e): + e(bot,cmd,nick,conn,public) + else: + e[0](bot,cmd,nick,conn,public,*e[1:]) diff --git a/define.patch b/define.patch new file mode 100644 index 0000000..eed52c4 --- /dev/null +++ b/define.patch @@ -0,0 +1,43 @@ +--- servus-orig/commands.py Thu Nov 21 01:26:06 2002 ++++ servus/commands.py Sat Jan 29 18:13:37 2005 +@@ -1,5 +1,5 @@ + # Part of Acrobat. +-import string, cPickle, random, urllib, sys, time ++import string, cPickle, random, urllib, sys, time, re + from irclib import irc_lower, nm_to_n + + # query karma +@@ -133,6 +133,33 @@ + bot.automsg(public,nick,str(gsearch)) + else: # we haven't found anything. + bot.automsg(public,nick,"No pages found.") ++ except IOError: # if the connection times out. This blocks. :( ++ bot.automsg(public,nick,"The web's broken. Waah!") ++ ++# Look up the definition of something using google ++def defineq(bot, cmd, nick, conn, public): ++ cmdrest = string.join(cmd.split()[1:]) ++ targ = ("http://www.google.com/search?q=define%%3A%s&ie=utf-8&oe=utf-8" ++ % urllib.quote_plus(cmdrest)) ++ try: ++ # Just slurp everything into a string ++ defnpage = urllib.urlopen(targ).read() ++ # For definitions we really do have to parse the HTML, sadly. ++ # This is of course going to be a bit fragile. We first look for ++ # 'Definitions of %s on the Web' -- if this isn't present we ++ # assume we have the 'no definitions found page'. ++ # The first defn starts after the following

tag. ++ # Following that we assume that each definition is all the non-markup ++ # before a
tag. Currently we just dump out the first definition. ++ match = re.search(r"Definitions of .*? on the Web.*?

\s*([^>]*)
",defnpage,re.MULTILINE) ++ if match == None: ++ bot.automsg(public,nick,"Some things defy definition.") ++ else: ++ # We assume google has truncated the definition for us so this ++ # won't flood the channel with text... ++ defn = " ".join(match.group(1).split("\n")); ++ bot.automsg(public,nick,defn) ++ + except IOError: # if the connection times out. This blocks. :( + bot.automsg(public,nick,"The web's broken. Waah!") + diff --git a/flirts.~1.12.~ b/flirts.~1.12.~ new file mode 100644 index 0000000..06a16e2 --- /dev/null +++ b/flirts.~1.12.~ @@ -0,0 +1,65 @@ +covers %s in blancmange +jumps for joy at the thought of %s +offers %s a bunch of flowers +blows kisses at %s +falls head over heels in love with %s +offers to take %s out for dinner +is %s's kind of robot +offers %s a Turkish Delight +gazes contentedly at %s +offers %s a cherry +imagines %s in a tutu +catches %s's eye across a crowded room +kisses %s's hand +wonders what a nice person like %s is doing in a place like this +licks chocolate sauce off %s +gets down on one knee in front of %s +bats its eyelashes at %s +winks suggestively at %s +blabbers incoherently at the sight of %s +mentally undresses %s +daydreams about %s +wonders what %s is really thinking +talks dirty to %s +gazes mutely into %s's eyes +wonders if %s might reciprocate its feelings +whispers sweet nothings to %s +sends %s a red rose +toasts %s with champagne +offers %s its last Rolo +invites %s up for coffee +promises %s a night to remember +invites %s in to see its etchings +wonders if %s is really that innocent +serenades %s +puts a rose between its teeth and tangoes seductively with %s +plays with its hair whilst gazing at %s +draws a magic circle around itself and %s +tickles %s playfully +draws a flattering portrait of %s +poings happily at the sight of %s +gives %s a high-five +brushes lint off %s's clothing +dims the lights for %s +invites %s behind the bike sheds +rides a tightrope on a unicycle while juggling chainsaws in an attempt to impress %s +composes haiku for %s +makes the tea for %s +strokes %s's hair +sprinkles %s with hundreds and thousands. +flagellates %s with a birch branch +<3s %s +admires %s's boots +invites %s to a candle-lit dinner +pole dances for %s +hopes %s packed their toothbrush! +showers %s in rose petals +holds a candle for %s +admires %s's physique +gives %s a nice relaxing foot massage +treks through the jungle to deliver Milk Tray to %s +searches for a rhyme for its poem about %s +compares %s's eyes to stars +wonders whether %s comes here often? +compares %s to a summer's day +curls up at %s's feet diff --git a/ircbot.py b/ircbot.py index ce229f2..cfb5bf8 100644 --- a/ircbot.py +++ b/ircbot.py @@ -205,7 +205,7 @@ class SingleServerIRCBot(SimpleIRCClient): Used when answering a CTCP VERSION request. """ - return "ircbot.py by Joel Rosdahl " + return "VERSION ircbot.py by Joel Rosdahl , Matthew Vernon and others" def jump_server(self): """Connect to a new server, possible disconnecting from the current. diff --git a/karmaconvert.py b/karmaconvert.py new file mode 100755 index 0000000..0971a23 --- /dev/null +++ b/karmaconvert.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python2 + +import cPickle,sys + +class Karma: + def __init__(self): + self.dict = {} + +f=open(sys.argv[1],"r") +karma=cPickle.load(f) +f.close() + +f=open(sys.argv[2],"w") +cPickle.dump(karma.dict,f) +f.close() diff --git a/launch.sh b/launch.sh new file mode 100755 index 0000000..5fd988e --- /dev/null +++ b/launch.sh @@ -0,0 +1,6 @@ +#!/bin/sh +python2.2 acrobat.py Servus-chiark & +python2.2 acrobat.py socbi & +python2.2 acrobat.py methsoc & +python2.2 acrobat.py methsoc-freenode & +python2.2 acrobat.py assassins & diff --git a/methsoc-freenode.py b/methsoc-freenode.py new file mode 100644 index 0000000..91d170c --- /dev/null +++ b/methsoc-freenode.py @@ -0,0 +1,173 @@ +# This file is part of Acrobat. +# +# Acrobat is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; either version 2 of the License, +# or (at your option) any later version. +# +# Acrobat is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Acrobat; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA. + +# Andrew Walkingshaw +# Peter Corbett +# Matthew Vernon +# Stephen Early + +# Acrobat configuration file + +# The following definitions are required to be present in this module: +server = "irc.eu.freenode.net" +port = 6667 +nickname = "Methsbot" +channel = "#methsoc" +owner = "Emperor" +# Also a function called "command"; see later. + +# Everything else in this file is configuration-specific. + +# Most command implementations are stored in a separate module. +import commands as c + +# This fishpond is shared between trouts and flirts. It doesn't have to be; +# you can define as many ponds as you like. +class fish: + cur_fish=5 + max_fish=5 + nofish_time=60 + fish_time_inc=60 + fish_inc=2 + DoS=0 + Boring_Git='Nobody' + quotatime=0 + +# load a file full of flirts or trouts +def __load(filename): + try: + f = open(filename, "r") + r = [l.strip() for l in f.readlines() if l.find("%s") != -1] + f.close() + except IOError: + r = [ "doesn't know what to do about %s." ] + return r + +# (troutlist,selftroutmsg,DoSmsg,notargetmsg,nofishmsg,fishpond,selftroutprob) +troutcfg = ( + __load("trouts"), + ' (at the instigation of %s)', + "Sorry, but %s is being a spoilsport.", + "Who do you wish me to trout?", + "Fish stocks exhausted.", + fish, + 0.1) + +flirtcfg = ( + __load("flirts"), + ' (but %s is their secret admirer)', + "Sorry, but %s made me take Holy Orders.", + "Who do you wish me to flirt with?", + "My libido is over-used!", + fish, + 0.1) + +# Hacky command to output the current fishpond state +def fishq(bot, cmd, nick, conn, public,f): + from irclib import irc_lower + if not public and irc_lower(nick) == irc_lower(bot.owner): + state=("Fishpond state: cur_fish=%d, max_fish=%d, nofish_time=%d, " + +"fish_time_inc=%d, fish_inc=%d, DoS=%d, Boring_Git=%s, " + +"quotatime=%d")%(f.cur_fish,f.max_fish,f.nofish_time, + f.fish_time_inc,f.fish_inc,f.DoS,f.Boring_Git, + f.quotatime) + bot.automsg(public,nick,state) + +# Karma implementation +import cPickle +karmafilename = "karmadump" +# load the karma db +try: + f = open(karmafilename, "r") + karmadb = cPickle.load(f) + f.close() +except IOError: + karmadb = {} +# Modify karma +def karma(cmd, amount): + thing=cmd.split()[0][:-2].lower() + if karmadb.has_key(thing): + karmadb[thing] += amount + else: + karmadb[thing] = amount +def savekarma(): + try: + f = open(karmafilename, "w") + cPickle.dump(karmadb, f) + f.close() + except IOError: + sys.stderr.write("Problems dumping karma: probably lost :(") + +# When the bot exits we should save the karma db +def quit(bot,cmd,nick,conn,public): + savekarma() + c.quitq(bot,cmd,nick,conn,public) +def reload(bot,cmd,nick,conn,public): + savekarma() + c.reloadq(bot,cmd,nick,conn,public) + +# Command processing: whenever something is said that the bot can hear, +# "command" is invoked and must decide what to do. This configuration +# defines a couple of special cases (for karma) but is otherwise driven +# by a dictionary of commands. + +commands = {"karma": (c.karmaq,karmadb), + "karmalist": (c.listkeysq,karmadb), + "karmadel": (c.karmadelq,karmadb), + "info": (c.infoq,karmadb), + "trout": (c.troutq,troutcfg), + "fish": (fishq,fish), + "flirt": (c.troutq,flirtcfg), + "quiet": (c.nofishq,fish), + "reload": reload, + "quit": quit, + "die": quit, + "google": c.googleq, + "say": c.sayq, + "do": c.doq } +# disconnect and hop annoy people +# "disconnect": c.disconnq, +# "hop": c.disconnq } +commands["list"]=(c.listkeysq,commands) + +triggers = ("!", "~") # what character should the bot be invoked by: + # eg !trout, ~trout etc. + +def command(bot, cmd, nick, conn, public): + ours=0 + try: + if public and cmd[0] in triggers: + ours=1 + cmd=cmd[1:] + if not public: + ours=1 + command = cmd.split()[0] + except IndexError: + command="" + # karma: up + if command.endswith("++"): + karma(cmd,1) + # karma: down + if command.endswith("--"): + karma(cmd,-1) + + if ours and command.lower() in commands.keys(): + e=commands[command] + if callable(e): + e(bot,cmd,nick,conn,public) + else: + e[0](bot,cmd,nick,conn,public,*e[1:]) diff --git a/methsoc.py b/methsoc.py new file mode 100644 index 0000000..e1f340c --- /dev/null +++ b/methsoc.py @@ -0,0 +1,173 @@ +# This file is part of Acrobat. +# +# Acrobat is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; either version 2 of the License, +# or (at your option) any later version. +# +# Acrobat is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Acrobat; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA. + +# Andrew Walkingshaw +# Peter Corbett +# Matthew Vernon +# Stephen Early + +# Acrobat configuration file + +# The following definitions are required to be present in this module: +server = "rapun" +port = 6667 +nickname = "Meths" +channel = "#methsoc" +owner = "Emperor" +# Also a function called "command"; see later. + +# Everything else in this file is configuration-specific. + +# Most command implementations are stored in a separate module. +import commands as c + +# This fishpond is shared between trouts and flirts. It doesn't have to be; +# you can define as many ponds as you like. +class fish: + cur_fish=5 + max_fish=5 + nofish_time=60 + fish_time_inc=60 + fish_inc=2 + DoS=0 + Boring_Git='Nobody' + quotatime=0 + +# load a file full of flirts or trouts +def __load(filename): + try: + f = open(filename, "r") + r = [l.strip() for l in f.readlines() if l.find("%s") != -1] + f.close() + except IOError: + r = [ "doesn't know what to do about %s." ] + return r + +# (troutlist,selftroutmsg,DoSmsg,notargetmsg,nofishmsg,fishpond,selftroutprob) +troutcfg = ( + __load("trouts"), + ' (at the instigation of %s)', + "Sorry, but %s is being a spoilsport.", + "Who do you wish me to trout?", + "Fish stocks exhausted.", + fish, + 0.1) + +flirtcfg = ( + __load("flirts"), + ' (but %s is their secret admirer)', + "Sorry, but %s made me take Holy Orders.", + "Who do you wish me to flirt with?", + "My libido is over-used!", + fish, + 0.1) + +# Hacky command to output the current fishpond state +def fishq(bot, cmd, nick, conn, public,f): + from irclib import irc_lower + if not public and irc_lower(nick) == irc_lower(bot.owner): + state=("Fishpond state: cur_fish=%d, max_fish=%d, nofish_time=%d, " + +"fish_time_inc=%d, fish_inc=%d, DoS=%d, Boring_Git=%s, " + +"quotatime=%d")%(f.cur_fish,f.max_fish,f.nofish_time, + f.fish_time_inc,f.fish_inc,f.DoS,f.Boring_Git, + f.quotatime) + bot.automsg(public,nick,state) + +# Karma implementation +import cPickle +karmafilename = "karmadump" +# load the karma db +try: + f = open(karmafilename, "r") + karmadb = cPickle.load(f) + f.close() +except IOError: + karmadb = {} +# Modify karma +def karma(cmd, amount): + thing=cmd.split()[0][:-2].lower() + if karmadb.has_key(thing): + karmadb[thing] += amount + else: + karmadb[thing] = amount +def savekarma(): + try: + f = open(karmafilename, "w") + cPickle.dump(karmadb, f) + f.close() + except IOError: + sys.stderr.write("Problems dumping karma: probably lost :(") + +# When the bot exits we should save the karma db +def quit(bot,cmd,nick,conn,public): + savekarma() + c.quitq(bot,cmd,nick,conn,public) +def reload(bot,cmd,nick,conn,public): + savekarma() + c.reloadq(bot,cmd,nick,conn,public) + +# Command processing: whenever something is said that the bot can hear, +# "command" is invoked and must decide what to do. This configuration +# defines a couple of special cases (for karma) but is otherwise driven +# by a dictionary of commands. + +commands = {"karma": (c.karmaq,karmadb), + "karmalist": (c.listkeysq,karmadb), + "karmadel": (c.karmadelq,karmadb), + "info": (c.infoq,karmadb), + "trout": (c.troutq,troutcfg), + "fish": (fishq,fish), + "flirt": (c.troutq,flirtcfg), + "quiet": (c.nofishq,fish), + "reload": reload, + "quit": quit, + "die": quit, + "google": c.googleq, + "say": c.sayq, + "do": c.doq } +# disconnect and hop annoy people +# "disconnect": c.disconnq, +# "hop": c.disconnq } +commands["list"]=(c.listkeysq,commands) + +triggers = ("!", "~") # what character should the bot be invoked by: + # eg !trout, ~trout etc. + +def command(bot, cmd, nick, conn, public): + ours=0 + try: + if public and cmd[0] in triggers: + ours=1 + cmd=cmd[1:] + if not public: + ours=1 + command = cmd.split()[0] + except IndexError: + command="" + # karma: up + if command.endswith("++"): + karma(cmd,1) + # karma: down + if command.endswith("--"): + karma(cmd,-1) + + if ours and command.lower() in commands.keys(): + e=commands[command] + if callable(e): + e(bot,cmd,nick,conn,public) + else: + e[0](bot,cmd,nick,conn,public,*e[1:]) diff --git a/rjk.diff b/rjk.diff new file mode 100644 index 0000000..5bd6bc8 --- /dev/null +++ b/rjk.diff @@ -0,0 +1,167 @@ +diff -ruN --exclude {arch} --exclude .arch-ids servus--dev--0--patch-2/ChangeLog.d/servus--dev--0 servus--dev--0--patch-4/ChangeLog.d/servus--dev--0 +--- servus--dev--0--patch-2/ChangeLog.d/servus--dev--0 2005-07-22 23:17:44.000000000 +0100 ++++ servus--dev--0--patch-4/ChangeLog.d/servus--dev--0 2005-07-22 23:17:45.000000000 +0100 +@@ -2,6 +2,35 @@ + # arch-tag: automatic-ChangeLog--rjk@greenend.org.uk--2004/servus--dev--0 + # + ++2005-07-22 22:02:48 GMT Richard Kettlewell patch-4 ++ ++ Summary: ++ bloody slashers ++ Revision: ++ servus--dev--0--patch-4 ++ ++ * commands.py: slashq command implements slashing of a pair of names ++ * Servus-chiark.py: configuration and setup for slash command ++ ++ modified files: ++ ChangeLog.d/servus--dev--0 Servus-chiark.py commands.py ++ ++ ++2005-07-22 21:36:36 GMT Richard Kettlewell patch-3 ++ ++ Summary: ++ fix karma storage ++ Revision: ++ servus--dev--0--patch-3 ++ ++ * Servus-chiark.py: save karma file each time it is changed, not just on ++ graceful termination; and rename it into place. ++ This prevents unnecessary forgetting of karma. ++ ++ modified files: ++ ChangeLog.d/servus--dev--0 Servus-chiark.py ++ ++ + 2005-07-22 21:29:40 GMT Richard Kettlewell patch-2 + + Summary: +diff -ruN --exclude {arch} --exclude .arch-ids servus--dev--0--patch-2/Servus-chiark.py servus--dev--0--patch-4/Servus-chiark.py +--- servus--dev--0--patch-2/Servus-chiark.py 2005-07-22 23:17:43.000000000 +0100 ++++ servus--dev--0--patch-4/Servus-chiark.py 2005-07-22 23:17:45.000000000 +0100 +@@ -19,6 +19,7 @@ + # Peter Corbett + # Matthew Vernon + # Stephen Early ++# Richard Kettlewell 2: ++ conn.notice(nick, "we'll have none of that round here") ++ return ++ me = bot.connection.get_nickname() ++ slash_msg = random.choice(fishlist) ++ # The bot won't slash people with themselves ++ if irc_lower(who[0]) == irc_lower(who[1]): ++ conn.notice(nick, "oooooh no missus!") ++ return ++ # The bot won't slash with itself, instead slashing the requester ++ for n in [0,1]: ++ if irc_lower(me) == irc_lower(who[n]): ++ who[n] = nick ++ # Perhaps someone asked to slash themselves with the bot then we get ++ if irc_lower(who[0]) == irc_lower(who[1]): ++ conn.notice(nick, "you wish!") ++ return ++ # There's a chance the game may be given away if the request was not ++ # public... ++ if not public: ++ if random.random()<=selfslashchance: ++ slash_msg=slash_msg+(selfslash%nick) ++ ++ bot.automsg(public,nick, slash_msg % (who[0], who[1])) ++ fishpond.cur_fish-=1 ++ + # Shut up trouting for a minute + def nofishq(bot, cmd, nick, conn, public, fish): + fish.cur_fish=0 diff --git a/slashes.~1.7.~ b/slashes.~1.7.~ new file mode 100644 index 0000000..93125e6 --- /dev/null +++ b/slashes.~1.7.~ @@ -0,0 +1,22 @@ +wonders what %s and %s are doing together in a locked room +wishes %s and %s would stop doing that in public +draws chibi fan art of %s and %s +embarks on the Good Ship %s-%s +watches %s and %s making the beast with two backs +thinks %s/%s is better than Legolas/Aragorn +is jealous because %s loves %s instead of itself +daydreams about %s and %s's wedding +writes plotless smut about %s's steamy secret liaison with %s +starts a Yahoo group devoted to Photoshops of %s and %s naked +gets horny just thinking about %s and %s together +thinks %s and %s should grow old together surrounded by their children +wonders who would be the godparents when %s and %s had children +wonders who would get pregnant, %s or %s +makes LJ banners proclaiming '%s and %s Is Love!' +makes animated LJ icons of %s and %s making out +wonders how %s and %s got together +wonders what %s and %s's friends would think of their relationship +writes dirty lyrics about %s and %s +sings, "%s and %s, sitting in a tree, k-i-s-s-i-n-g" +sets up %s and %s on a blind date +thinks %s and %s should get a room! diff --git a/slashes~ b/slashes~ new file mode 100644 index 0000000..5b3d0e8 --- /dev/null +++ b/slashes~ @@ -0,0 +1,2 @@ +wonders what %s and %s are doing together in a locked room +wishes %s and %s would stop doing that in public diff --git a/socbi.py b/socbi.py new file mode 100644 index 0000000..da7d343 --- /dev/null +++ b/socbi.py @@ -0,0 +1,173 @@ +# This file is part of Acrobat. +# +# Acrobat is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; either version 2 of the License, +# or (at your option) any later version. +# +# Acrobat is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Acrobat; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA. + +# Andrew Walkingshaw +# Peter Corbett +# Matthew Vernon +# Stephen Early + +# Acrobat configuration file + +# The following definitions are required to be present in this module: +server = "irc.eu.freenode.net" +port = 6667 +nickname = "HBB" +channel = "#soc.bi" +owner = "Emperor" +# Also a function called "command"; see later. + +# Everything else in this file is configuration-specific. + +# Most command implementations are stored in a separate module. +import commands as c + +# This fishpond is shared between trouts and flirts. It doesn't have to be; +# you can define as many ponds as you like. +class fish: + cur_fish=5 + max_fish=5 + nofish_time=60 + fish_time_inc=60 + fish_inc=2 + DoS=0 + Boring_Git='Nobody' + quotatime=0 + +# load a file full of flirts or trouts +def __load(filename): + try: + f = open(filename, "r") + r = [l.strip() for l in f.readlines() if l.find("%s") != -1] + f.close() + except IOError: + r = [ "doesn't know what to do about %s." ] + return r + +# (troutlist,selftroutmsg,DoSmsg,notargetmsg,nofishmsg,fishpond,selftroutprob) +troutcfg = ( + __load("trouts"), + ' (at the instigation of %s)', + "Sorry, but %s is being a spoilsport.", + "Who do you wish me to trout?", + "Fish stocks exhausted.", + fish, + 0.1) + +flirtcfg = ( + __load("flirts"), + ' (but %s is their secret admirer)', + "Sorry, but %s made me take Holy Orders.", + "Who do you wish me to flirt with?", + "My libido is over-used!", + fish, + 0.1) + +# Hacky command to output the current fishpond state +def fishq(bot, cmd, nick, conn, public,f): + from irclib import irc_lower + if not public and irc_lower(nick) == irc_lower(bot.owner): + state=("Fishpond state: cur_fish=%d, max_fish=%d, nofish_time=%d, " + +"fish_time_inc=%d, fish_inc=%d, DoS=%d, Boring_Git=%s, " + +"quotatime=%d")%(f.cur_fish,f.max_fish,f.nofish_time, + f.fish_time_inc,f.fish_inc,f.DoS,f.Boring_Git, + f.quotatime) + bot.automsg(public,nick,state) + +# Karma implementation +import cPickle +karmafilename = "karmadump" +# load the karma db +try: + f = open(karmafilename, "r") + karmadb = cPickle.load(f) + f.close() +except IOError: + karmadb = {} +# Modify karma +def karma(cmd, amount): + thing=cmd.split()[0][:-2].lower() + if karmadb.has_key(thing): + karmadb[thing] += amount + else: + karmadb[thing] = amount +def savekarma(): + try: + f = open(karmafilename, "w") + cPickle.dump(karmadb, f) + f.close() + except IOError: + sys.stderr.write("Problems dumping karma: probably lost :(") + +# When the bot exits we should save the karma db +def quit(bot,cmd,nick,conn,public): + savekarma() + c.quitq(bot,cmd,nick,conn,public) +def reload(bot,cmd,nick,conn,public): + savekarma() + c.reloadq(bot,cmd,nick,conn,public) + +# Command processing: whenever something is said that the bot can hear, +# "command" is invoked and must decide what to do. This configuration +# defines a couple of special cases (for karma) but is otherwise driven +# by a dictionary of commands. + +commands = {"karma": (c.karmaq,karmadb), + "karmalist": (c.listkeysq,karmadb), + "karmadel": (c.karmadelq,karmadb), + "info": (c.infoq,karmadb), + "trout": (c.troutq,troutcfg), + "fish": (fishq,fish), + "flirt": (c.troutq,flirtcfg), + "quiet": (c.nofishq,fish), + "reload": reload, + "quit": quit, + "die": quit, + "google": c.googleq, + "say": c.sayq, + "do": c.doq } +# disconnect and hop annoy people +# "disconnect": c.disconnq, +# "hop": c.disconnq } +commands["list"]=(c.listkeysq,commands) + +triggers = ("!", "~") # what character should the bot be invoked by: + # eg !trout, ~trout etc. + +def command(bot, cmd, nick, conn, public): + ours=0 + try: + if public and cmd[0] in triggers: + ours=1 + cmd=cmd[1:] + if not public: + ours=1 + command = cmd.split()[0] + except IndexError: + command="" + # karma: up + if command.endswith("++"): + karma(cmd,1) + # karma: down + if command.endswith("--"): + karma(cmd,-1) + + if ours and command.lower() in commands.keys(): + e=commands[command] + if callable(e): + e(bot,cmd,nick,conn,public) + else: + e[0](bot,cmd,nick,conn,public,*e[1:]) diff --git a/test-chiark.py b/test-chiark.py new file mode 100644 index 0000000..f985079 --- /dev/null +++ b/test-chiark.py @@ -0,0 +1,207 @@ +# This file is part of Acrobat. +# +# Acrobat is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; either version 2 of the License, +# or (at your option) any later version. +# +# Acrobat is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Acrobat; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA. + +# Andrew Walkingshaw +# Peter Corbett +# Matthew Vernon +# Stephen Early +# Richard Kettlewell game.losetime: #we randomly lost, take new trigger + c.gameq(bot,cmd,bot.owner,conn,False,game) + diff --git a/test-chiark.py~ b/test-chiark.py~ new file mode 100644 index 0000000..9c6d2ef --- /dev/null +++ b/test-chiark.py~ @@ -0,0 +1,206 @@ +# This file is part of Acrobat. +# +# Acrobat is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; either version 2 of the License, +# or (at your option) any later version. +# +# Acrobat is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Acrobat; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA. + +# Andrew Walkingshaw +# Peter Corbett +# Matthew Vernon +# Stephen Early +# Richard Kettlewell game.losetime: #we randomly lost, take new trigger + c.gameq(bot,cmd,bot.owner,conn,False,game) + diff --git a/trouts.~1.49.~ b/trouts.~1.49.~ new file mode 100644 index 0000000..1d1c614 --- /dev/null +++ b/trouts.~1.49.~ @@ -0,0 +1,159 @@ +hits %s with a wet trout. +thwaps %s with a five-mile-long spacestation. +sacrifices %s to dark gods. +bites his thumb at %s. +replaces %s with a 2-line shell script +takes off and nukes %s from orbit +treats %s with the contempt they deserve +grumbles sonorously at %s +offers %s a one way ticket to Mars +decapitates %s +slaps %s with a wet haddock +plays the xylophone on %s's kneecaps +lands a minke whale on %s +inverts %s +sends %s to Cuba +searches frantically for %s's brain +introduces %s to their antimatter twin +opens a trapdoor under %s +lands a 10 ton weight on %s +pokes %s with a spoon +hits %s with a squeaky hammer +wonders if %s has a return-to-base warranty +mutters darkly about %s +thwaps %s unimaginatively +staples %s to Gordon Brown's nose +growls at %s +catapults %s over the Atlantic +replaces %s with a papier mache replica +attaches an extra three noses to %s +eviscerates %s with a trowel +annihilates %s +wonders what God was thinking of when He made %s +removes %s from the gene pool +performs a lube-less rectal on %s +uses %s to practice his orthopedic surgery on +thinks %s would make excellent road-kill +plays basketball with %s's head +drops %s down a deep well +covers %s in blancmange +napalms %s +finds %s particularly irksome +feeds %s to his pet dog +reminds %s that life is not a game of Nethack +thinks that %s would give a cannibal nausea +wonders if %s is ever going to become a swan +drops an anvil on %s from a great height +disparages %s's mother +blows raspberries, blackberries and oranges at %s +would eat %s's brains but can't find them +thinks %s could do with less coffee +stuffs %s into a small box and nails down the lid +bites %s's ankles +pulls a moonie at %s +throws %s a bone +invites %s to go jump +throws jelly at %s +throws %s from the top of the empire state building +drops a small hydrogen bomb on %s +curses %s unto the seventh generation +asks if %s is a man or a mouse +questions the lineage of %s +accuses %s of being a goth +creeps up on %s wielding a crush emasculator +watches %s splashing around in the shallow end of the gene pool +swashbuckles the +2 Blessed Trout of Doom in %s's face +runs over %s with a tank +throws a rotten head of cabbage at %s +flagellates %s with a birch branch +sends %s to Coventry +recoils from %s as if they have the plague +kicks %s into touch +teleports %s to the Dimension of Pain +pours sour milk into %s's coffee +drops a ferret into %s's clothing +throws %s into the Cam +tries to steal %s's dinner money +attaches a debugger to %s +introduces %s to the business end of half a wasp +drives a manure spreader past %s +goes flying with %s tied to a wing +throws an explosive banana at %s +subscribes %s to 1001 junk mail lists +points at %s and laughs +dices %s and feeds them to Ross Anderson +follows %s around in a black helicopter +dissects %s in public +threatens %s with a rectal thermometer +thwaps %s with IWJ's +10 rod of deprecation +buys %s a fake RAF moustache +earnestly discusses railway routing regulations with %s +folds %s into an elaborate origami representation of a mackerel +reports %s to the RIAA +hands %s a live grenade +throws a cat at %s +kidnaps %s and fills their shoes with custard +plays Britney Spears songs at %s +fills %s's ears with butter +inverts %s +throws %s to the lions +sacrifices %s for the greater good +patents %s's genome. +covers %s in attack hamsters. +brandishes a spoon at %s. +searches %s for weapons of mass destruction. +smears %s in blood and waits for the sharks. +cackles malevolently at %s. +clones %s and eats the original. +teaches %s to think inside a very small box. +looms at %s. +calls down an airstrike upon %s. +feeds %s to the all-powerful Sarlacc. +affixes live crocodile clips to %s. +causes %s to have acute razor burn. +places the fleas of a thousand camels in %s's armpit. +sticks %s's head on a spike. +brings %s's haddock to paddock +breaks %s up for spare parts +tags %s as 'wontfix' +tags %s as 'obsolete' +boils %s up for glue +nails %s into a barrel and pushes them over Niagara Falls +accuses %s of losing The Game +douses %s in liquid nitrogen +turns %s inside-out +paints %s red and waits for the bulls +signs %s up for a Scientology course +shrieks hysterically at %s +declares %s an unlawful combatant +vetoes %s +rubs bubblegum in %s's hair +steals all %s's coffee +heckles %s +inflates %s and brandishes a pin +thinks %s is worse than Microsoft Windows +wonders why %s bothered getting out of bed today +trolls %s +runs %s through +plays the harmonica at %s +bricks %s up with a cask of Amontillado +lands a TARDIS on %s +practises acupuncture on %s +rugby-tackles %s to the ground +passes %s the underpants of shame +removes %s's willy with a chainsaw +throws a custard pie at %s +violates %s's mind (and body) with the Necronomicon +applies TCP_CORK to %s +performs a capsaicin enema upon %s. +rejects %s in favour of the null hypothesis. +makes %s illegal. +compiles %s with -trigraphs +compiles %s with -Wtrigraphs +turns %s into dogfood +><> ><> %s +sets %s and robhu up on a blind date +gently taps %s with a menhir. +invests all of %s's money in a high-grade structured credit strategy enhanced leverage fund. +plays the viola at %s. +thinks %s is comparable to iprocurement at its best diff --git a/urllib.py b/urllib.py new file mode 100644 index 0000000..cd4876d --- /dev/null +++ b/urllib.py @@ -0,0 +1,1461 @@ +"""Open an arbitrary URL. + +See the following document for more info on URLs: +"Names and Addresses, URIs, URLs, URNs, URCs", at +http://www.w3.org/pub/WWW/Addressing/Overview.html + +See also the HTTP spec (from which the error codes are derived): +"HTTP - Hypertext Transfer Protocol", at +http://www.w3.org/pub/WWW/Protocols/ + +Related standards and specs: +- RFC1808: the "relative URL" spec. (authoritative status) +- RFC1738 - the "URL standard". (authoritative status) +- RFC1630 - the "URI spec". (informational status) + +The object returned by URLopener().open(file) will differ per +protocol. All you know is that is has methods read(), readline(), +readlines(), fileno(), close() and info(). The read*(), fileno() +and close() methods work like those of open files. +The info() method returns a mimetools.Message object which can be +used to query various info about the object, if available. +(mimetools.Message objects are queried with the getheader() method.) +""" + +import string +import socket +import os +import stat +import time +import sys +import types + +__all__ = ["urlopen", "URLopener", "FancyURLopener", "urlretrieve", + "urlcleanup", "quote", "quote_plus", "unquote", "unquote_plus", + "urlencode", "url2pathname", "pathname2url", "splittag", + "localhost", "thishost", "ftperrors", "basejoin", "unwrap", + "splittype", "splithost", "splituser", "splitpasswd", "splitport", + "splitnport", "splitquery", "splitattr", "splitvalue", + "splitgophertype", "getproxies"] + +__version__ = '1.15' # XXX This version is not always updated :-( + +MAXFTPCACHE = 10 # Trim the ftp cache beyond this size + +# Helper for non-unix systems +if os.name == 'mac': + from macurl2path import url2pathname, pathname2url +elif os.name == 'nt': + from nturl2path import url2pathname, pathname2url +elif os.name == 'riscos': + from rourl2path import url2pathname, pathname2url +else: + def url2pathname(pathname): + return unquote(pathname) + def pathname2url(pathname): + return quote(pathname) + +# This really consists of two pieces: +# (1) a class which handles opening of all sorts of URLs +# (plus assorted utilities etc.) +# (2) a set of functions for parsing URLs +# XXX Should these be separated out into different modules? + + +# Shortcut for basic usage +_urlopener = None +def urlopen(url, data=None): + """urlopen(url [, data]) -> open file-like object""" + global _urlopener + if not _urlopener: + _urlopener = FancyURLopener() + if data is None: + return _urlopener.open(url) + else: + return _urlopener.open(url, data) +def urlretrieve(url, filename=None, reporthook=None, data=None): + global _urlopener + if not _urlopener: + _urlopener = FancyURLopener() + return _urlopener.retrieve(url, filename, reporthook, data) +def urlcleanup(): + if _urlopener: + _urlopener.cleanup() + + +ftpcache = {} +class URLopener: + """Class to open URLs. + This is a class rather than just a subroutine because we may need + more than one set of global protocol-specific options. + Note -- this is a base class for those who don't want the + automatic handling of errors type 302 (relocated) and 401 + (authorization needed).""" + + __tempfiles = None + + version = "Python-urllib/%s" % __version__ + + # Constructor + def __init__(self, proxies=None, **x509): + if proxies is None: + proxies = getproxies() + assert hasattr(proxies, 'has_key'), "proxies must be a mapping" + self.proxies = proxies + self.key_file = x509.get('key_file') + self.cert_file = x509.get('cert_file') + self.addheaders = [('User-agent', 'Servus/0.2')] + self.__tempfiles = [] + self.__unlink = os.unlink # See cleanup() + self.tempcache = None + # Undocumented feature: if you assign {} to tempcache, + # it is used to cache files retrieved with + # self.retrieve(). This is not enabled by default + # since it does not work for changing documents (and I + # haven't got the logic to check expiration headers + # yet). + self.ftpcache = ftpcache + # Undocumented feature: you can use a different + # ftp cache by assigning to the .ftpcache member; + # in case you want logically independent URL openers + # XXX This is not threadsafe. Bah. + + def __del__(self): + self.close() + + def close(self): + self.cleanup() + + def cleanup(self): + # This code sometimes runs when the rest of this module + # has already been deleted, so it can't use any globals + # or import anything. + if self.__tempfiles: + for file in self.__tempfiles: + try: + self.__unlink(file) + except OSError: + pass + del self.__tempfiles[:] + if self.tempcache: + self.tempcache.clear() + + def addheader(self, *args): + """Add a header to be used by the HTTP interface only + e.g. u.addheader('Accept', 'sound/basic')""" + self.addheaders.append(args) + + # External interface + def open(self, fullurl, data=None): + """Use URLopener().open(file) instead of open(file, 'r').""" + fullurl = unwrap(toBytes(fullurl)) + if self.tempcache and self.tempcache.has_key(fullurl): + filename, headers = self.tempcache[fullurl] + fp = open(filename, 'rb') + return addinfourl(fp, headers, fullurl) + urltype, url = splittype(fullurl) + if not urltype: + urltype = 'file' + if self.proxies.has_key(urltype): + proxy = self.proxies[urltype] + urltype, proxyhost = splittype(proxy) + host, selector = splithost(proxyhost) + url = (host, fullurl) # Signal special case to open_*() + else: + proxy = None + name = 'open_' + urltype + self.type = urltype + if '-' in name: + # replace - with _ + name = '_'.join(name.split('-')) + if not hasattr(self, name): + if proxy: + return self.open_unknown_proxy(proxy, fullurl, data) + else: + return self.open_unknown(fullurl, data) + try: + if data is None: + return getattr(self, name)(url) + else: + return getattr(self, name)(url, data) + except socket.error, msg: + raise IOError, ('socket error', msg), sys.exc_info()[2] + + def open_unknown(self, fullurl, data=None): + """Overridable interface to open unknown URL type.""" + type, url = splittype(fullurl) + raise IOError, ('url error', 'unknown url type', type) + + def open_unknown_proxy(self, proxy, fullurl, data=None): + """Overridable interface to open unknown URL type.""" + type, url = splittype(fullurl) + raise IOError, ('url error', 'invalid proxy for %s' % type, proxy) + + # External interface + def retrieve(self, url, filename=None, reporthook=None, data=None): + """retrieve(url) returns (filename, None) for a local object + or (tempfilename, headers) for a remote object.""" + url = unwrap(toBytes(url)) + if self.tempcache and self.tempcache.has_key(url): + return self.tempcache[url] + type, url1 = splittype(url) + if not filename and (not type or type == 'file'): + try: + fp = self.open_local_file(url1) + hdrs = fp.info() + del fp + return url2pathname(splithost(url1)[1]), hdrs + except IOError, msg: + pass + fp = self.open(url, data) + headers = fp.info() + if not filename: + import tempfile + garbage, path = splittype(url) + garbage, path = splithost(path or "") + path, garbage = splitquery(path or "") + path, garbage = splitattr(path or "") + suffix = os.path.splitext(path)[1] + filename = tempfile.mktemp(suffix) + self.__tempfiles.append(filename) + result = filename, headers + if self.tempcache is not None: + self.tempcache[url] = result + tfp = open(filename, 'wb') + bs = 1024*8 + size = -1 + blocknum = 1 + if reporthook: + if headers.has_key("content-length"): + size = int(headers["Content-Length"]) + reporthook(0, bs, size) + block = fp.read(bs) + if reporthook: + reporthook(1, bs, size) + while block: + tfp.write(block) + block = fp.read(bs) + blocknum = blocknum + 1 + if reporthook: + reporthook(blocknum, bs, size) + fp.close() + tfp.close() + del fp + del tfp + return result + + # Each method named open_ knows how to open that type of URL + + def open_http(self, url, data=None): + """Use HTTP protocol.""" + import httplib + user_passwd = None + if type(url) is types.StringType: + host, selector = splithost(url) + if host: + user_passwd, host = splituser(host) + host = unquote(host) + realhost = host + else: + host, selector = url + urltype, rest = splittype(selector) + url = rest + user_passwd = None + if urltype.lower() != 'http': + realhost = None + else: + realhost, rest = splithost(rest) + if realhost: + user_passwd, realhost = splituser(realhost) + if user_passwd: + selector = "%s://%s%s" % (urltype, realhost, rest) + if proxy_bypass(realhost): + host = realhost + + #print "proxy via http:", host, selector + if not host: raise IOError, ('http error', 'no host given') + if user_passwd: + import base64 + auth = base64.encodestring(user_passwd).strip() + else: + auth = None + h = httplib.HTTP(host) + if data is not None: + h.putrequest('POST', selector) + h.putheader('Content-type', 'application/x-www-form-urlencoded') + h.putheader('Content-length', '%d' % len(data)) + else: + h.putrequest('GET', selector) + if auth: h.putheader('Authorization', 'Basic %s' % auth) + if realhost: h.putheader('Host', realhost) + for args in self.addheaders: apply(h.putheader, args) + h.endheaders() + if data is not None: + h.send(data) + errcode, errmsg, headers = h.getreply() + fp = h.getfile() + if errcode == 200: + return addinfourl(fp, headers, "http:" + url) + else: + if data is None: + return self.http_error(url, fp, errcode, errmsg, headers) + else: + return self.http_error(url, fp, errcode, errmsg, headers, data) + + def http_error(self, url, fp, errcode, errmsg, headers, data=None): + """Handle http errors. + Derived class can override this, or provide specific handlers + named http_error_DDD where DDD is the 3-digit error code.""" + # First check if there's a specific handler for this error + name = 'http_error_%d' % errcode + if hasattr(self, name): + method = getattr(self, name) + if data is None: + result = method(url, fp, errcode, errmsg, headers) + else: + result = method(url, fp, errcode, errmsg, headers, data) + if result: return result + return self.http_error_default(url, fp, errcode, errmsg, headers) + + def http_error_default(self, url, fp, errcode, errmsg, headers): + """Default error handler: close the connection and raise IOError.""" + void = fp.read() + fp.close() + raise IOError, ('http error', errcode, errmsg, headers) + + if hasattr(socket, "ssl"): + def open_https(self, url, data=None): + """Use HTTPS protocol.""" + import httplib + user_passwd = None + if type(url) is types.StringType: + host, selector = splithost(url) + if host: + user_passwd, host = splituser(host) + host = unquote(host) + realhost = host + else: + host, selector = url + urltype, rest = splittype(selector) + url = rest + user_passwd = None + if urltype.lower() != 'https': + realhost = None + else: + realhost, rest = splithost(rest) + if realhost: + user_passwd, realhost = splituser(realhost) + if user_passwd: + selector = "%s://%s%s" % (urltype, realhost, rest) + #print "proxy via https:", host, selector + if not host: raise IOError, ('https error', 'no host given') + if user_passwd: + import base64 + auth = base64.encodestring(user_passwd).strip() + else: + auth = None + h = httplib.HTTPS(host, 0, + key_file=self.key_file, + cert_file=self.cert_file) + if data is not None: + h.putrequest('POST', selector) + h.putheader('Content-type', + 'application/x-www-form-urlencoded') + h.putheader('Content-length', '%d' % len(data)) + else: + h.putrequest('GET', selector) + if auth: h.putheader('Authorization: Basic %s' % auth) + if realhost: h.putheader('Host', realhost) + for args in self.addheaders: apply(h.putheader, args) + h.endheaders() + if data is not None: + h.send(data) + errcode, errmsg, headers = h.getreply() + fp = h.getfile() + if errcode == 200: + return addinfourl(fp, headers, "https:" + url) + else: + if data is None: + return self.http_error(url, fp, errcode, errmsg, headers) + else: + return self.http_error(url, fp, errcode, errmsg, headers, + data) + + def open_gopher(self, url): + """Use Gopher protocol.""" + import gopherlib + host, selector = splithost(url) + if not host: raise IOError, ('gopher error', 'no host given') + host = unquote(host) + type, selector = splitgophertype(selector) + selector, query = splitquery(selector) + selector = unquote(selector) + if query: + query = unquote(query) + fp = gopherlib.send_query(selector, query, host) + else: + fp = gopherlib.send_selector(selector, host) + return addinfourl(fp, noheaders(), "gopher:" + url) + + def open_file(self, url): + """Use local file or FTP depending on form of URL.""" + if url[:2] == '//' and url[2:3] != '/': + return self.open_ftp(url) + else: + return self.open_local_file(url) + + def open_local_file(self, url): + """Use local file.""" + import mimetypes, mimetools, rfc822, StringIO + host, file = splithost(url) + localname = url2pathname(file) + try: + stats = os.stat(localname) + except OSError, e: + raise IOError(e.errno, e.strerror, e.filename) + size = stats[stat.ST_SIZE] + modified = rfc822.formatdate(stats[stat.ST_MTIME]) + mtype = mimetypes.guess_type(url)[0] + headers = mimetools.Message(StringIO.StringIO( + 'Content-Type: %s\nContent-Length: %d\nLast-modified: %s\n' % + (mtype or 'text/plain', size, modified))) + if not host: + urlfile = file + if file[:1] == '/': + urlfile = 'file://' + file + return addinfourl(open(localname, 'rb'), + headers, urlfile) + host, port = splitport(host) + if not port \ + and socket.gethostbyname(host) in (localhost(), thishost()): + urlfile = file + if file[:1] == '/': + urlfile = 'file://' + file + return addinfourl(open(localname, 'rb'), + headers, urlfile) + raise IOError, ('local file error', 'not on local host') + + def open_ftp(self, url): + """Use FTP protocol.""" + import mimetypes, mimetools, StringIO + host, path = splithost(url) + if not host: raise IOError, ('ftp error', 'no host given') + host, port = splitport(host) + user, host = splituser(host) + if user: user, passwd = splitpasswd(user) + else: passwd = None + host = unquote(host) + user = unquote(user or '') + passwd = unquote(passwd or '') + host = socket.gethostbyname(host) + if not port: + import ftplib + port = ftplib.FTP_PORT + else: + port = int(port) + path, attrs = splitattr(path) + path = unquote(path) + dirs = path.split('/') + dirs, file = dirs[:-1], dirs[-1] + if dirs and not dirs[0]: dirs = dirs[1:] + if dirs and not dirs[0]: dirs[0] = '/' + key = user, host, port, '/'.join(dirs) + # XXX thread unsafe! + if len(self.ftpcache) > MAXFTPCACHE: + # Prune the cache, rather arbitrarily + for k in self.ftpcache.keys(): + if k != key: + v = self.ftpcache[k] + del self.ftpcache[k] + v.close() + try: + if not self.ftpcache.has_key(key): + self.ftpcache[key] = \ + ftpwrapper(user, passwd, host, port, dirs) + if not file: type = 'D' + else: type = 'I' + for attr in attrs: + attr, value = splitvalue(attr) + if attr.lower() == 'type' and \ + value in ('a', 'A', 'i', 'I', 'd', 'D'): + type = value.upper() + (fp, retrlen) = self.ftpcache[key].retrfile(file, type) + mtype = mimetypes.guess_type("ftp:" + url)[0] + headers = "" + if mtype: + headers += "Content-Type: %s\n" % mtype + if retrlen is not None and retrlen >= 0: + headers += "Content-Length: %d\n" % retrlen + headers = mimetools.Message(StringIO.StringIO(headers)) + return addinfourl(fp, headers, "ftp:" + url) + except ftperrors(), msg: + raise IOError, ('ftp error', msg), sys.exc_info()[2] + + def open_data(self, url, data=None): + """Use "data" URL.""" + # ignore POSTed data + # + # syntax of data URLs: + # dataurl := "data:" [ mediatype ] [ ";base64" ] "," data + # mediatype := [ type "/" subtype ] *( ";" parameter ) + # data := *urlchar + # parameter := attribute "=" value + import StringIO, mimetools, time + try: + [type, data] = url.split(',', 1) + except ValueError: + raise IOError, ('data error', 'bad data URL') + if not type: + type = 'text/plain;charset=US-ASCII' + semi = type.rfind(';') + if semi >= 0 and '=' not in type[semi:]: + encoding = type[semi+1:] + type = type[:semi] + else: + encoding = '' + msg = [] + msg.append('Date: %s'%time.strftime('%a, %d %b %Y %T GMT', + time.gmtime(time.time()))) + msg.append('Content-type: %s' % type) + if encoding == 'base64': + import base64 + data = base64.decodestring(data) + else: + data = unquote(data) + msg.append('Content-length: %d' % len(data)) + msg.append('') + msg.append(data) + msg = '\n'.join(msg) + f = StringIO.StringIO(msg) + headers = mimetools.Message(f, 0) + f.fileno = None # needed for addinfourl + return addinfourl(f, headers, url) + + +class FancyURLopener(URLopener): + """Derived class with handlers for errors we can handle (perhaps).""" + + def __init__(self, *args): + apply(URLopener.__init__, (self,) + args) + self.auth_cache = {} + self.tries = 0 + self.maxtries = 10 + + def http_error_default(self, url, fp, errcode, errmsg, headers): + """Default error handling -- don't raise an exception.""" + return addinfourl(fp, headers, "http:" + url) + + def http_error_302(self, url, fp, errcode, errmsg, headers, data=None): + """Error 302 -- relocated (temporarily).""" + self.tries += 1 + if self.maxtries and self.tries >= self.maxtries: + if hasattr(self, "http_error_500"): + meth = self.http_error_500 + else: + meth = self.http_error_default + self.tries = 0 + return meth(url, fp, 500, + "Internal Server Error: Redirect Recursion", headers) + result = self.redirect_internal(url, fp, errcode, errmsg, headers, + data) + self.tries = 0 + return result + + def redirect_internal(self, url, fp, errcode, errmsg, headers, data): + if headers.has_key('location'): + newurl = headers['location'] + elif headers.has_key('uri'): + newurl = headers['uri'] + else: + return + void = fp.read() + fp.close() + # In case the server sent a relative URL, join with original: + newurl = basejoin(self.type + ":" + url, newurl) + if data is None: + return self.open(newurl) + else: + return self.open(newurl, data) + + def http_error_301(self, url, fp, errcode, errmsg, headers, data=None): + """Error 301 -- also relocated (permanently).""" + return self.http_error_302(url, fp, errcode, errmsg, headers, data) + + def http_error_401(self, url, fp, errcode, errmsg, headers, data=None): + """Error 401 -- authentication required. + See this URL for a description of the basic authentication scheme: + http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-v10-spec-00.txt""" + if not headers.has_key('www-authenticate'): + URLopener.http_error_default(self, url, fp, + errcode, errmsg, headers) + stuff = headers['www-authenticate'] + import re + match = re.match('[ \t]*([^ \t]+)[ \t]+realm="([^"]*)"', stuff) + if not match: + URLopener.http_error_default(self, url, fp, + errcode, errmsg, headers) + scheme, realm = match.groups() + if scheme.lower() != 'basic': + URLopener.http_error_default(self, url, fp, + errcode, errmsg, headers) + name = 'retry_' + self.type + '_basic_auth' + if data is None: + return getattr(self,name)(url, realm) + else: + return getattr(self,name)(url, realm, data) + + def retry_http_basic_auth(self, url, realm, data=None): + host, selector = splithost(url) + i = host.find('@') + 1 + host = host[i:] + user, passwd = self.get_user_passwd(host, realm, i) + if not (user or passwd): return None + host = quote(user, safe='') + ':' + quote(passwd, safe='') + '@' + host + newurl = 'http://' + host + selector + if data is None: + return self.open(newurl) + else: + return self.open(newurl, data) + + def retry_https_basic_auth(self, url, realm, data=None): + host, selector = splithost(url) + i = host.find('@') + 1 + host = host[i:] + user, passwd = self.get_user_passwd(host, realm, i) + if not (user or passwd): return None + host = quote(user, safe='') + ':' + quote(passwd, safe='') + '@' + host + newurl = '//' + host + selector + return self.open_https(newurl, data) + + def get_user_passwd(self, host, realm, clear_cache = 0): + key = realm + '@' + host.lower() + if self.auth_cache.has_key(key): + if clear_cache: + del self.auth_cache[key] + else: + return self.auth_cache[key] + user, passwd = self.prompt_user_passwd(host, realm) + if user or passwd: self.auth_cache[key] = (user, passwd) + return user, passwd + + def prompt_user_passwd(self, host, realm): + """Override this in a GUI environment!""" + import getpass + try: + user = raw_input("Enter username for %s at %s: " % (realm, + host)) + passwd = getpass.getpass("Enter password for %s in %s at %s: " % + (user, realm, host)) + return user, passwd + except KeyboardInterrupt: + print + return None, None + + +# Utility functions + +_localhost = None +def localhost(): + """Return the IP address of the magic hostname 'localhost'.""" + global _localhost + if not _localhost: + _localhost = socket.gethostbyname('localhost') + return _localhost + +_thishost = None +def thishost(): + """Return the IP address of the current host.""" + global _thishost + if not _thishost: + _thishost = socket.gethostbyname(socket.gethostname()) + return _thishost + +_ftperrors = None +def ftperrors(): + """Return the set of errors raised by the FTP class.""" + global _ftperrors + if not _ftperrors: + import ftplib + _ftperrors = ftplib.all_errors + return _ftperrors + +_noheaders = None +def noheaders(): + """Return an empty mimetools.Message object.""" + global _noheaders + if not _noheaders: + import mimetools + import StringIO + _noheaders = mimetools.Message(StringIO.StringIO(), 0) + _noheaders.fp.close() # Recycle file descriptor + return _noheaders + + +# Utility classes + +class ftpwrapper: + """Class used by open_ftp() for cache of open FTP connections.""" + + def __init__(self, user, passwd, host, port, dirs): + self.user = user + self.passwd = passwd + self.host = host + self.port = port + self.dirs = dirs + self.init() + + def init(self): + import ftplib + self.busy = 0 + self.ftp = ftplib.FTP() + self.ftp.connect(self.host, self.port) + self.ftp.login(self.user, self.passwd) + for dir in self.dirs: + self.ftp.cwd(dir) + + def retrfile(self, file, type): + import ftplib + self.endtransfer() + if type in ('d', 'D'): cmd = 'TYPE A'; isdir = 1 + else: cmd = 'TYPE ' + type; isdir = 0 + try: + self.ftp.voidcmd(cmd) + except ftplib.all_errors: + self.init() + self.ftp.voidcmd(cmd) + conn = None + if file and not isdir: + # Use nlst to see if the file exists at all + try: + self.ftp.nlst(file) + except ftplib.error_perm, reason: + raise IOError, ('ftp error', reason), sys.exc_info()[2] + # Restore the transfer mode! + self.ftp.voidcmd(cmd) + # Try to retrieve as a file + try: + cmd = 'RETR ' + file + conn = self.ftp.ntransfercmd(cmd) + except ftplib.error_perm, reason: + if str(reason)[:3] != '550': + raise IOError, ('ftp error', reason), sys.exc_info()[2] + if not conn: + # Set transfer mode to ASCII! + self.ftp.voidcmd('TYPE A') + # Try a directory listing + if file: cmd = 'LIST ' + file + else: cmd = 'LIST' + conn = self.ftp.ntransfercmd(cmd) + self.busy = 1 + # Pass back both a suitably decorated object and a retrieval length + return (addclosehook(conn[0].makefile('rb'), + self.endtransfer), conn[1]) + def endtransfer(self): + if not self.busy: + return + self.busy = 0 + try: + self.ftp.voidresp() + except ftperrors(): + pass + + def close(self): + self.endtransfer() + try: + self.ftp.close() + except ftperrors(): + pass + +class addbase: + """Base class for addinfo and addclosehook.""" + + def __init__(self, fp): + self.fp = fp + self.read = self.fp.read + self.readline = self.fp.readline + if hasattr(self.fp, "readlines"): self.readlines = self.fp.readlines + if hasattr(self.fp, "fileno"): self.fileno = self.fp.fileno + + def __repr__(self): + return '<%s at %s whose fp = %s>' % (self.__class__.__name__, + `id(self)`, `self.fp`) + + def close(self): + self.read = None + self.readline = None + self.readlines = None + self.fileno = None + if self.fp: self.fp.close() + self.fp = None + +class addclosehook(addbase): + """Class to add a close hook to an open file.""" + + def __init__(self, fp, closehook, *hookargs): + addbase.__init__(self, fp) + self.closehook = closehook + self.hookargs = hookargs + + def close(self): + addbase.close(self) + if self.closehook: + apply(self.closehook, self.hookargs) + self.closehook = None + self.hookargs = None + +class addinfo(addbase): + """class to add an info() method to an open file.""" + + def __init__(self, fp, headers): + addbase.__init__(self, fp) + self.headers = headers + + def info(self): + return self.headers + +class addinfourl(addbase): + """class to add info() and geturl() methods to an open file.""" + + def __init__(self, fp, headers, url): + addbase.__init__(self, fp) + self.headers = headers + self.url = url + + def info(self): + return self.headers + + def geturl(self): + return self.url + + +def basejoin(base, url): + """Utility to combine a URL with a base URL to form a new URL.""" + type, path = splittype(url) + if type: + # if url is complete (i.e., it contains a type), return it + return url + host, path = splithost(path) + type, basepath = splittype(base) # inherit type from base + if host: + # if url contains host, just inherit type + if type: return type + '://' + host + path + else: + # no type inherited, so url must have started with // + # just return it + return url + host, basepath = splithost(basepath) # inherit host + basepath, basetag = splittag(basepath) # remove extraneous cruft + basepath, basequery = splitquery(basepath) # idem + if path[:1] != '/': + # non-absolute path name + if path[:1] in ('#', '?'): + # path is just a tag or query, attach to basepath + i = len(basepath) + else: + # else replace last component + i = basepath.rfind('/') + if i < 0: + # basepath not absolute + if host: + # host present, make absolute + basepath = '/' + else: + # else keep non-absolute + basepath = '' + else: + # remove last file component + basepath = basepath[:i+1] + # Interpret ../ (important because of symlinks) + while basepath and path[:3] == '../': + path = path[3:] + i = basepath[:-1].rfind('/') + if i > 0: + basepath = basepath[:i+1] + elif i == 0: + basepath = '/' + break + else: + basepath = '' + + path = basepath + path + if host and path and path[0] != '/': + path = '/' + path + if type and host: return type + '://' + host + path + elif type: return type + ':' + path + elif host: return '//' + host + path # don't know what this means + else: return path + + +# Utilities to parse URLs (most of these return None for missing parts): +# unwrap('') --> 'type://host/path' +# splittype('type:opaquestring') --> 'type', 'opaquestring' +# splithost('//host[:port]/path') --> 'host[:port]', '/path' +# splituser('user[:passwd]@host[:port]') --> 'user[:passwd]', 'host[:port]' +# splitpasswd('user:passwd') -> 'user', 'passwd' +# splitport('host:port') --> 'host', 'port' +# splitquery('/path?query') --> '/path', 'query' +# splittag('/path#tag') --> '/path', 'tag' +# splitattr('/path;attr1=value1;attr2=value2;...') -> +# '/path', ['attr1=value1', 'attr2=value2', ...] +# splitvalue('attr=value') --> 'attr', 'value' +# splitgophertype('/Xselector') --> 'X', 'selector' +# unquote('abc%20def') -> 'abc def' +# quote('abc def') -> 'abc%20def') + +def toBytes(url): + """toBytes(u"URL") --> 'URL'.""" + # Most URL schemes require ASCII. If that changes, the conversion + # can be relaxed + if type(url) is types.UnicodeType: + try: + url = url.encode("ASCII") + except UnicodeError: + raise UnicodeError("URL " + repr(url) + + " contains non-ASCII characters") + return url + +def unwrap(url): + """unwrap('') --> 'type://host/path'.""" + url = url.strip() + if url[:1] == '<' and url[-1:] == '>': + url = url[1:-1].strip() + if url[:4] == 'URL:': url = url[4:].strip() + return url + +_typeprog = None +def splittype(url): + """splittype('type:opaquestring') --> 'type', 'opaquestring'.""" + global _typeprog + if _typeprog is None: + import re + _typeprog = re.compile('^([^/:]+):') + + match = _typeprog.match(url) + if match: + scheme = match.group(1) + return scheme.lower(), url[len(scheme) + 1:] + return None, url + +_hostprog = None +def splithost(url): + """splithost('//host[:port]/path') --> 'host[:port]', '/path'.""" + global _hostprog + if _hostprog is None: + import re + _hostprog = re.compile('^//([^/]*)(.*)$') + + match = _hostprog.match(url) + if match: return match.group(1, 2) + return None, url + +_userprog = None +def splituser(host): + """splituser('user[:passwd]@host[:port]') --> 'user[:passwd]', 'host[:port]'.""" + global _userprog + if _userprog is None: + import re + _userprog = re.compile('^([^@]*)@(.*)$') + + match = _userprog.match(host) + if match: return map(unquote, match.group(1, 2)) + return None, host + +_passwdprog = None +def splitpasswd(user): + """splitpasswd('user:passwd') -> 'user', 'passwd'.""" + global _passwdprog + if _passwdprog is None: + import re + _passwdprog = re.compile('^([^:]*):(.*)$') + + match = _passwdprog.match(user) + if match: return match.group(1, 2) + return user, None + +# splittag('/path#tag') --> '/path', 'tag' +_portprog = None +def splitport(host): + """splitport('host:port') --> 'host', 'port'.""" + global _portprog + if _portprog is None: + import re + _portprog = re.compile('^(.*):([0-9]+)$') + + match = _portprog.match(host) + if match: return match.group(1, 2) + return host, None + +_nportprog = None +def splitnport(host, defport=-1): + """Split host and port, returning numeric port. + Return given default port if no ':' found; defaults to -1. + Return numerical port if a valid number are found after ':'. + Return None if ':' but not a valid number.""" + global _nportprog + if _nportprog is None: + import re + _nportprog = re.compile('^(.*):(.*)$') + + match = _nportprog.match(host) + if match: + host, port = match.group(1, 2) + try: + if not port: raise ValueError, "no digits" + nport = int(port) + except ValueError: + nport = None + return host, nport + return host, defport + +_queryprog = None +def splitquery(url): + """splitquery('/path?query') --> '/path', 'query'.""" + global _queryprog + if _queryprog is None: + import re + _queryprog = re.compile('^(.*)\?([^?]*)$') + + match = _queryprog.match(url) + if match: return match.group(1, 2) + return url, None + +_tagprog = None +def splittag(url): + """splittag('/path#tag') --> '/path', 'tag'.""" + global _tagprog + if _tagprog is None: + import re + _tagprog = re.compile('^(.*)#([^#]*)$') + + match = _tagprog.match(url) + if match: return match.group(1, 2) + return url, None + +def splitattr(url): + """splitattr('/path;attr1=value1;attr2=value2;...') -> + '/path', ['attr1=value1', 'attr2=value2', ...].""" + words = url.split(';') + return words[0], words[1:] + +_valueprog = None +def splitvalue(attr): + """splitvalue('attr=value') --> 'attr', 'value'.""" + global _valueprog + if _valueprog is None: + import re + _valueprog = re.compile('^([^=]*)=(.*)$') + + match = _valueprog.match(attr) + if match: return match.group(1, 2) + return attr, None + +def splitgophertype(selector): + """splitgophertype('/Xselector') --> 'X', 'selector'.""" + if selector[:1] == '/' and selector[1:2]: + return selector[1], selector[2:] + return None, selector + +def unquote(s): + """unquote('abc%20def') -> 'abc def'.""" + mychr = chr + myatoi = int + list = s.split('%') + res = [list[0]] + myappend = res.append + del list[0] + for item in list: + if item[1:2]: + try: + myappend(mychr(myatoi(item[:2], 16)) + + item[2:]) + except ValueError: + myappend('%' + item) + else: + myappend('%' + item) + return "".join(res) + +def unquote_plus(s): + """unquote('%7e/abc+def') -> '~/abc def'""" + if '+' in s: + # replace '+' with ' ' + s = ' '.join(s.split('+')) + return unquote(s) + +always_safe = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 'abcdefghijklmnopqrstuvwxyz' + '0123456789' '_.-') + +_fast_safe_test = always_safe + '/' +_fast_safe = None + +def _fast_quote(s): + global _fast_safe + if _fast_safe is None: + _fast_safe = {} + for c in _fast_safe_test: + _fast_safe[c] = c + res = list(s) + for i in range(len(res)): + c = res[i] + if not _fast_safe.has_key(c): + res[i] = '%%%02X' % ord(c) + return ''.join(res) + +def quote(s, safe = '/'): + """quote('abc def') -> 'abc%20def' + + Each part of a URL, e.g. the path info, the query, etc., has a + different set of reserved characters that must be quoted. + + RFC 2396 Uniform Resource Identifiers (URI): Generic Syntax lists + the following reserved characters. + + reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | + "$" | "," + + Each of these characters is reserved in some component of a URL, + but not necessarily in all of them. + + By default, the quote function is intended for quoting the path + section of a URL. Thus, it will not encode '/'. This character + is reserved, but in typical usage the quote function is being + called on a path where the existing slash characters are used as + reserved characters. + """ + safe = always_safe + safe + if _fast_safe_test == safe: + return _fast_quote(s) + res = list(s) + for i in range(len(res)): + c = res[i] + if c not in safe: + res[i] = '%%%02X' % ord(c) + return ''.join(res) + +def quote_plus(s, safe = ''): + """Quote the query fragment of a URL; replacing ' ' with '+'""" + if ' ' in s: + l = s.split(' ') + for i in range(len(l)): + l[i] = quote(l[i], safe) + return '+'.join(l) + else: + return quote(s, safe) + +def urlencode(query,doseq=0): + """Encode a sequence of two-element tuples or dictionary into a URL query string. + + If any values in the query arg are sequences and doseq is true, each + sequence element is converted to a separate parameter. + + If the query arg is a sequence of two-element tuples, the order of the + parameters in the output will match the order of parameters in the + input. + """ + + if hasattr(query,"items"): + # mapping objects + query = query.items() + else: + # it's a bother at times that strings and string-like objects are + # sequences... + try: + # non-sequence items should not work with len() + x = len(query) + # non-empty strings will fail this + if len(query) and type(query[0]) != types.TupleType: + raise TypeError + # zero-length sequences of all types will get here and succeed, + # but that's a minor nit - since the original implementation + # allowed empty dicts that type of behavior probably should be + # preserved for consistency + except TypeError: + ty,va,tb = sys.exc_info() + raise TypeError, "not a valid non-string sequence or mapping object", tb + + l = [] + if not doseq: + # preserve old behavior + for k, v in query: + k = quote_plus(str(k)) + v = quote_plus(str(v)) + l.append(k + '=' + v) + else: + for k, v in query: + k = quote_plus(str(k)) + if type(v) == types.StringType: + v = quote_plus(v) + l.append(k + '=' + v) + elif type(v) == types.UnicodeType: + # is there a reasonable way to convert to ASCII? + # encode generates a string, but "replace" or "ignore" + # lose information and "strict" can raise UnicodeError + v = quote_plus(v.encode("ASCII","replace")) + l.append(k + '=' + v) + else: + try: + # is this a sufficient test for sequence-ness? + x = len(v) + except TypeError: + # not a sequence + v = quote_plus(str(v)) + l.append(k + '=' + v) + else: + # loop over the sequence + for elt in v: + l.append(k + '=' + quote_plus(str(elt))) + return '&'.join(l) + +# Proxy handling +def getproxies_environment(): + """Return a dictionary of scheme -> proxy server URL mappings. + + Scan the environment for variables named _proxy; + this seems to be the standard convention. If you need a + different way, you can pass a proxies dictionary to the + [Fancy]URLopener constructor. + + """ + proxies = {} + for name, value in os.environ.items(): + name = name.lower() + if value and name[-6:] == '_proxy': + proxies[name[:-6]] = value + return proxies + +if os.name == 'mac': + def getproxies(): + """Return a dictionary of scheme -> proxy server URL mappings. + + By convention the mac uses Internet Config to store + proxies. An HTTP proxy, for instance, is stored under + the HttpProxy key. + + """ + try: + import ic + except ImportError: + return {} + + try: + config = ic.IC() + except ic.error: + return {} + proxies = {} + # HTTP: + if config.has_key('UseHTTPProxy') and config['UseHTTPProxy']: + try: + value = config['HTTPProxyHost'] + except ic.error: + pass + else: + proxies['http'] = 'http://%s' % value + # FTP: XXXX To be done. + # Gopher: XXXX To be done. + return proxies + + def proxy_bypass(x): + return 0 + +elif os.name == 'nt': + def getproxies_registry(): + """Return a dictionary of scheme -> proxy server URL mappings. + + Win32 uses the registry to store proxies. + + """ + proxies = {} + try: + import _winreg + except ImportError: + # Std module, so should be around - but you never know! + return proxies + try: + internetSettings = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, + r'Software\Microsoft\Windows\CurrentVersion\Internet Settings') + proxyEnable = _winreg.QueryValueEx(internetSettings, + 'ProxyEnable')[0] + if proxyEnable: + # Returned as Unicode but problems if not converted to ASCII + proxyServer = str(_winreg.QueryValueEx(internetSettings, + 'ProxyServer')[0]) + if '=' in proxyServer: + # Per-protocol settings + for p in proxyServer.split(';'): + protocol, address = p.split('=', 1) + # See if address has a type:// prefix + import re + if not re.match('^([^/:]+)://', address): + address = '%s://%s' % (protocol, address) + proxies[protocol] = address + else: + # Use one setting for all protocols + if proxyServer[:5] == 'http:': + proxies['http'] = proxyServer + else: + proxies['http'] = 'http://%s' % proxyServer + proxies['ftp'] = 'ftp://%s' % proxyServer + internetSettings.Close() + except (WindowsError, ValueError, TypeError): + # Either registry key not found etc, or the value in an + # unexpected format. + # proxies already set up to be empty so nothing to do + pass + return proxies + + def getproxies(): + """Return a dictionary of scheme -> proxy server URL mappings. + + Returns settings gathered from the environment, if specified, + or the registry. + + """ + return getproxies_environment() or getproxies_registry() + + def proxy_bypass(host): + try: + import _winreg + import re + import socket + except ImportError: + # Std modules, so should be around - but you never know! + return 0 + try: + internetSettings = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, + r'Software\Microsoft\Windows\CurrentVersion\Internet Settings') + proxyEnable = _winreg.QueryValueEx(internetSettings, + 'ProxyEnable')[0] + proxyOverride = str(_winreg.QueryValueEx(internetSettings, + 'ProxyOverride')[0]) + # ^^^^ Returned as Unicode but problems if not converted to ASCII + except WindowsError: + return 0 + if not proxyEnable or not proxyOverride: + return 0 + # try to make a host list from name and IP address. + host = [host] + try: + addr = socket.gethostbyname(host[0]) + if addr != host: + host.append(addr) + except socket.error: + pass + # make a check value list from the registry entry: replace the + # '' string by the localhost entry and the corresponding + # canonical entry. + proxyOverride = proxyOverride.split(';') + i = 0 + while i < len(proxyOverride): + if proxyOverride[i] == '': + proxyOverride[i:i+1] = ['localhost', + '127.0.0.1', + socket.gethostname(), + socket.gethostbyname( + socket.gethostname())] + i += 1 + # print proxyOverride + # now check if we match one of the registry values. + for test in proxyOverride: + test = test.replace(".", r"\.") # mask dots + test = test.replace("*", r".*") # change glob sequence + test = test.replace("?", r".") # change glob char + for val in host: + # print "%s <--> %s" %( test, val ) + if re.match(test, val, re.I): + return 1 + return 0 + +else: + # By default use environment variables + getproxies = getproxies_environment + + def proxy_bypass(host): + return 0 + +# Test and time quote() and unquote() +def test1(): + import time + s = '' + for i in range(256): s = s + chr(i) + s = s*4 + t0 = time.time() + qs = quote(s) + uqs = unquote(qs) + t1 = time.time() + if uqs != s: + print 'Wrong!' + print `s` + print `qs` + print `uqs` + print round(t1 - t0, 3), 'sec' + + +def reporthook(blocknum, blocksize, totalsize): + # Report during remote transfers + print "Block number: %d, Block size: %d, Total size: %d" % ( + blocknum, blocksize, totalsize) + +# Test program +def test(args=[]): + if not args: + args = [ + '/etc/passwd', + 'file:/etc/passwd', + 'file://localhost/etc/passwd', + 'ftp://ftp.python.org/pub/python/README', +## 'gopher://gopher.micro.umn.edu/1/', + 'http://www.python.org/index.html', + ] + if hasattr(URLopener, "open_https"): + args.append('https://synergy.as.cmu.edu/~geek/') + try: + for url in args: + print '-'*10, url, '-'*10 + fn, h = urlretrieve(url, None, reporthook) + print fn + if h: + print '======' + for k in h.keys(): print k + ':', h[k] + print '======' + fp = open(fn, 'rb') + data = fp.read() + del fp + if '\r' in data: + table = string.maketrans("", "") + data = data.translate(table, "\r") + print data + fn, h = None, None + print '-'*40 + finally: + urlcleanup() + +def main(): + import getopt, sys + try: + opts, args = getopt.getopt(sys.argv[1:], "th") + except getopt.error, msg: + print msg + print "Use -h for help" + return + t = 0 + for o, a in opts: + if o == '-t': + t = t + 1 + if o == '-h': + print "Usage: python urllib.py [-t] [url ...]" + print "-t runs self-test;", + print "otherwise, contents of urls are printed" + return + if t: + if t > 1: + test1() + test(args) + else: + if not args: + print "Use -h for help" + for url in args: + print urlopen(url).read(), + +# Run test program when run as a script +if __name__ == '__main__': + main()