--- /dev/null
+# 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 <andrew@lexical.org.uk>
+# Peter Corbett <ptc24@cam.ac.uk>
+# Matthew Vernon <matthew@debian.org>
+# Stephen Early <steve@greenend.org.uk>
+# Richard Kettlewell <rjk@greenend.org.uk
+
+# Acrobat configuration file
+
+# The following definitions are required to be present in this module:
+server = "irc.nsict.org"
+port = 6667
+nickname = "Servus"
+channel = "#artichoke"
+owner = "Emperor"
+# Also a function called "command"; see later.
+
+# Everything else in this file is configuration-specific.
+
+import os, time
+
+# 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)
+
+slashcfg= (
+ __load("slashes"),
+ ' (while %s watches)',
+ "Sorry, but %s stole my pen.",
+ "Who do you want to slash?",
+ "I have writer's block!",
+ 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 = "artichoke-karma"
+# 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
+ savekarma()
+def savekarma():
+ tmp = "%s.tmp" % karmafilename
+ try:
+ f = open(tmp, "w")
+ cPickle.dump(karmadb, f)
+ f.close()
+ os.rename(tmp, karmafilename)
+ except IOError, e:
+ sys.stderr.write("error writing karma: %s" % e)
+
+def quit(bot,cmd,nick,conn,public):
+ c.quitq(bot,cmd,nick,conn,public)
+def reload(bot,cmd,nick,conn,public):
+ c.reloadq(bot,cmd,nick,conn,public)
+
+#The game...
+class game:
+ trigger="Servus"
+ grace=time.time()
+ minlose=24*60*60 #1 day
+ maxlose=14*minlose #2 weeks
+ losetime=time.time()+300000
+
+# 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),
+ "slash": (c.slashq, slashcfg),
+ "rot13": c.rot13q,
+ "fish": (fishq,fish),
+ "flirt": (c.troutq,flirtcfg),
+ "quiet": (c.nofishq,fish),
+ "reload": reload,
+ "quit": quit,
+ "die": quit,
+ "google": c.googleq,
+# "define": c.defineq,
+# "game": (c.gameq,game),
+ "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.lower()]
+ if callable(e):
+ e(bot,cmd,nick,conn,public)
+ else:
+ e[0](bot,cmd,nick,conn,public,*e[1:])
+# elif public:
+# if cmd.find("GAME")!=-1: #someone else lost
+# grace.grace=time.time()+60*20
+# elif cmd.find(game.trigger)!=-1 and len(game.trigger)>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)
+#
--- /dev/null
+# 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 <andrew@lexical.org.uk>
+# Peter Corbett <ptc24@cam.ac.uk>
+# Matthew Vernon <matthew@debian.org>
+# Stephen Early <steve@greenend.org.uk>
+# Richard Kettlewell <rjk@greenend.org.uk
+
+# 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.
+
+import os, time
+
+# 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)
+
+slashcfg= (
+ __load("slashes"),
+ ' (while %s watches)',
+ "Sorry, but %s stole my pen.",
+ "Who do you want to slash?",
+ "I have writer's block!",
+ 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 = "chiark-karma"
+# 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
+ savekarma()
+def savekarma():
+ tmp = "%s.tmp" % karmafilename
+ try:
+ f = open(tmp, "w")
+ cPickle.dump(karmadb, f)
+ f.close()
+ os.rename(tmp, karmafilename)
+ except IOError, e:
+ sys.stderr.write("error writing karma: %s" % e)
+
+def quit(bot,cmd,nick,conn,public):
+ c.quitq(bot,cmd,nick,conn,public)
+def reload(bot,cmd,nick,conn,public):
+ c.reloadq(bot,cmd,nick,conn,public)
+
+#The game...
+class game:
+ trigger="Servus"
+ grace=time.time()
+ minlose=24*60*60 #1 day
+ maxlose=14*minlose #2 weeks
+ losetime=time.time()+300000
+
+# 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),
+ "slash": (c.slashq, slashcfg),
+ "rot13": c.rot13q,
+ "fish": (fishq,fish),
+ "flirt": (c.troutq,flirtcfg),
+ "quiet": (c.nofishq,fish),
+ "reload": reload,
+ "quit": quit,
+ "die": quit,
+ "google": c.googleq,
+# "define": c.defineq,
+# "game": (c.gameq,game),
+ "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.lower()]
+ if callable(e):
+ e(bot,cmd,nick,conn,public)
+ else:
+ e[0](bot,cmd,nick,conn,public,*e[1:])
+# elif public:
+# if cmd.find("GAME")!=-1: #someone else lost
+# grace.grace=time.time()+60*20
+# elif cmd.find(game.trigger)!=-1 and len(game.trigger)>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)
+#
--- /dev/null
+# 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 <andrew@lexical.org.uk>
+# Peter Corbett <ptc24@cam.ac.uk>
+# Matthew Vernon <matthew@debian.org>
+# Stephen Early <steve@greenend.org.uk>
+# Richard Kettlewell <rjk@greenend.org.uk
+
+# 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.
+
+import os, time
+
+# 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)
+
+slashcfg= (
+ __load("slashes"),
+ ' (while %s watches)',
+ "Sorry, but %s stole my pen.",
+ "Who do you want to slash?",
+ "I have writer's block!",
+ 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 = "chiark-karma"
+# 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
+ savekarma()
+def savekarma():
+ tmp = "%s.tmp" % karmafilename
+ try:
+ f = open(tmp, "w")
+ cPickle.dump(karmadb, f)
+ f.close()
+ os.rename(tmp, karmafilename)
+ except IOError, e:
+ sys.stderr.write("error writing karma: %s" % e)
+
+def quit(bot,cmd,nick,conn,public):
+ c.quitq(bot,cmd,nick,conn,public)
+def reload(bot,cmd,nick,conn,public):
+ c.reloadq(bot,cmd,nick,conn,public)
+
+#The game...
+class game:
+ trigger="Servus"
+ grace=time.time()
+ minlose=24*60*60 #1 day
+ maxlose=14*minlose #2 weeks
+ losetime=time.time()+300000
+
+# 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),
+ "slash": (c.slashq, slashcfg),
+ "rot13": c.rot13q,
+ "fish": (fishq,fish),
+ "flirt": (c.troutq,flirtcfg),
+ "quiet": (c.nofishq,fish),
+ "reload": reload,
+ "quit": quit,
+ "die": quit,
+ "google": c.googleq,
+ "units": c.unitq,
+# "define": c.defineq,
+# "game": (c.gameq,game),
+ "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.lower()]
+ if callable(e):
+ e(bot,cmd,nick,conn,public)
+ else:
+ e[0](bot,cmd,nick,conn,public,*e[1:])
+# elif public:
+# if cmd.find("GAME")!=-1: #someone else lost
+# grace.grace=time.time()+60*20
+# elif cmd.find(game.trigger)!=-1 and len(game.trigger)>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)
+#
--- /dev/null
+# 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 <andrew@lexical.org.uk>
+# Peter Corbett <ptc24@cam.ac.uk>
+# Matthew Vernon <matthew@debian.org>
+# Stephen Early <steve@greenend.org.uk>
+# Richard Kettlewell <rjk@greenend.org.uk
+
+# 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.
+
+import os, time
+
+# 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)
+
+slashcfg= (
+ __load("slashes"),
+ ' (while %s watches)',
+ "Sorry, but %s stole my pen.",
+ "Who do you want to slash?",
+ "I have writer's block!",
+ 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 = "chiark-karma"
+# 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
+ savekarma()
+def savekarma():
+ tmp = "%s.tmp" % karmafilename
+ try:
+ f = open(tmp, "w")
+ cPickle.dump(karmadb, f)
+ f.close()
+ os.rename(tmp, karmafilename)
+ except IOError, e:
+ sys.stderr.write("error writing karma: %s" % e)
+
+def quit(bot,cmd,nick,conn,public):
+ c.quitq(bot,cmd,nick,conn,public)
+def reload(bot,cmd,nick,conn,public):
+ c.reloadq(bot,cmd,nick,conn,public)
+
+#The game...
+class game:
+ trigger="Servus"
+ grace=time.time()
+ minlose=24*60*60 #1 day
+ maxlose=14*minlose #2 weeks
+ losetime=time.time()+300000
+
+# 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),
+ "slash": (c.slashq, slashcfg),
+ "rot13": c.rot13q,
+ "fish": (fishq,fish),
+ "flirt": (c.troutq,flirtcfg),
+ "quiet": (c.nofishq,fish),
+ "reload": reload,
+ "quit": quit,
+ "die": quit,
+ "google": c.googleq,
+ "units": c.unitq,
+# "define": c.defineq,
+# "game": (c.gameq,game),
+ "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.lower()]
+ if callable(e):
+ e(bot,cmd,nick,conn,public)
+ else:
+ e[0](bot,cmd,nick,conn,public,*e[1:])
+# elif public:
+# if cmd.find("GAME")!=-1: #someone else lost
+# grace.grace=time.time()+60*20
+# elif cmd.find(game.trigger)!=-1 and len(game.trigger)>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)
+#
# Contributors:
# Peter Corbett <ptc24@cam.ac.uk>
# Matthew Vernon <matthew@debian.org>
+#
+# Stephen Early <steve@greenend.org.uk> mostly deleted stuff
#
# This file is part of Acrobat.
#
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):
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 <server[:port]> <channel> <nickname> 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()
--- /dev/null
+# 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 <andrew@lexical.org.uk>
+# Peter Corbett <ptc24@cam.ac.uk>
+# Matthew Vernon <matthew@debian.org>
+# Stephen Early <steve@greenend.org.uk>
+
+# 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:])
--- /dev/null
+# 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 <andrew@lexical.org.uk>
+# Peter Corbett <ptc24@cam.ac.uk>
+# Matthew Vernon <matthew@debian.org>
+# Stephen Early <steve@greenend.org.uk>
+
+# 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:])
# 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))
# 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 <p> tag.
+ # Following that we assume that each definition is all the non-markup
+ # before a <br> tag. Currently we just dump out the first definition.
+ match = re.search(r"Definitions of <b>.*?</b> on the Web.*?<p>\s*([^>]*)<br>",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))
--- /dev/null
+# 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()))
--- /dev/null
+***************
+*** 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 <p> tag.
++ # Following that we assume that each definition is all the non-markup
++ # before a <br> tag. Currently we just dump out the first definition.
++ match = re.search(r"Definitions of <b>.*?</b> on the Web.*?<p>\s*([^>]*)<br>",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!")
+
--- /dev/null
+# 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 <p> tag.
+ # Following that we assume that each definition is all the non-markup
+ # before a <br> tag. Currently we just dump out the first definition.
+ match = re.search(r"Definitions of <b>.*?</b> on the Web.*?<p>\s*([^>]*)<br>",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))
# Andrew Walkingshaw <andrew@lexical.org.uk>
# Peter Corbett <ptc24@cam.ac.uk>
# Matthew Vernon <matthew@debian.org>
+# Stephen Early <steve@greenend.org.uk>
# 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:])
--- /dev/null
+# 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 <andrew@lexical.org.uk>
+# Peter Corbett <ptc24@cam.ac.uk>
+# Matthew Vernon <matthew@debian.org>
+# Stephen Early <steve@greenend.org.uk>
+
+# 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:])
--- /dev/null
+--- 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 <p> tag.
++ # Following that we assume that each definition is all the non-markup
++ # before a <br> tag. Currently we just dump out the first definition.
++ match = re.search(r"Definitions of <b>.*?</b> on the Web.*?<p>\s*([^>]*)<br>",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!")
+
--- /dev/null
+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
\r
Used when answering a CTCP VERSION request.\r
"""\r
- return "ircbot.py by Joel Rosdahl <joel@rosdahl.net>"\r
+ return "VERSION ircbot.py by Joel Rosdahl <joel@rosdahl.net>, Matthew Vernon <matthew@debian.org> and others"\r
\r
def jump_server(self):\r
"""Connect to a new server, possible disconnecting from the current.\r
--- /dev/null
+#!/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()
--- /dev/null
+#!/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 &
--- /dev/null
+# 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 <andrew@lexical.org.uk>
+# Peter Corbett <ptc24@cam.ac.uk>
+# Matthew Vernon <matthew@debian.org>
+# Stephen Early <steve@greenend.org.uk>
+
+# 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:])
--- /dev/null
+# 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 <andrew@lexical.org.uk>
+# Peter Corbett <ptc24@cam.ac.uk>
+# Matthew Vernon <matthew@debian.org>
+# Stephen Early <steve@greenend.org.uk>
+
+# 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:])
--- /dev/null
+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 <rjk@greenend.org.uk> 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 <rjk@greenend.org.uk> 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 <rjk@greenend.org.uk> 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 <ptc24@cam.ac.uk>
+ # Matthew Vernon <matthew@debian.org>
+ # Stephen Early <steve@greenend.org.uk>
++# Richard Kettlewell <rjk@greenend.org.uk
+
+ # Acrobat configuration file
+
+@@ -32,6 +33,8 @@
+
+ # Everything else in this file is configuration-specific.
+
++import os
++
+ # Most command implementations are stored in a separate module.
+ import commands as c
+
+@@ -76,6 +79,15 @@
+ fish,
+ 0.1)
+
++slashcfg= (
++ __load("slashes"),
++ ' (while %s watches)',
++ "Sorry, but %s stole my pen.",
++ "Who do you want to slash?",
++ "I have writer's block!",
++ fish,
++ 0.1)
++
+ # Hacky command to output the current fishpond state
+ def fishq(bot, cmd, nick, conn, public,f):
+ from irclib import irc_lower
+@@ -104,20 +116,20 @@
+ karmadb[thing] += amount
+ else:
+ karmadb[thing] = amount
++ savekarma()
+ def savekarma():
++ tmp = "%s.tmp" % karmafilename
+ try:
+- f = open(karmafilename, "w")
++ f = open(tmp, "w")
+ cPickle.dump(karmadb, f)
+ f.close()
+- except IOError:
+- sys.stderr.write("Problems dumping karma: probably lost :(")
++ os.rename(tmp, karmafilename)
++ except IOError, e:
++ sys.stderr.write("error writing karma: %s" % e)
+
+-# 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,
+@@ -130,6 +142,7 @@
+ "karmadel": (c.karmadelq,karmadb),
+ "info": (c.infoq,karmadb),
+ "trout": (c.troutq,troutcfg),
++ "slash": (c.slashq, slashcfg),
+ "fish": (fishq,fish),
+ "flirt": (c.troutq,flirtcfg),
+ "quiet": (c.nofishq,fish),
+diff -ruN --exclude {arch} --exclude .arch-ids servus--dev--0--patch-2/commands.py servus--dev--0--patch-4/commands.py
+--- servus--dev--0--patch-2/commands.py 2005-07-22 23:17:43.000000000 +0100
++++ servus--dev--0--patch-4/commands.py 2005-07-22 23:17:45.000000000 +0100
+@@ -90,6 +90,53 @@
+ 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]
++
++ 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:]
++ 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)
++
++ 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
--- /dev/null
+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!
--- /dev/null
+wonders what %s and %s are doing together in a locked room
+wishes %s and %s would stop doing that in public
--- /dev/null
+# 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 <andrew@lexical.org.uk>
+# Peter Corbett <ptc24@cam.ac.uk>
+# Matthew Vernon <matthew@debian.org>
+# Stephen Early <steve@greenend.org.uk>
+
+# 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:])
--- /dev/null
+# 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 <andrew@lexical.org.uk>
+# Peter Corbett <ptc24@cam.ac.uk>
+# Matthew Vernon <matthew@debian.org>
+# Stephen Early <steve@greenend.org.uk>
+# Richard Kettlewell <rjk@greenend.org.uk
+
+# 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.
+
+import os, time
+
+# 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)
+
+slashcfg= (
+ __load("slashes"),
+ ' (while %s watches)',
+ "Sorry, but %s stole my pen.",
+ "Who do you want to slash?",
+ "I have writer's block!",
+ 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 = "chiark-test-karma"
+# 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
+ savekarma()
+def savekarma():
+ tmp = "%s.tmp" % karmafilename
+ try:
+ f = open(tmp, "w")
+ cPickle.dump(karmadb, f)
+ f.close()
+ os.rename(tmp, karmafilename)
+ except IOError, e:
+ sys.stderr.write("error writing karma: %s" % e)
+
+def quit(bot,cmd,nick,conn,public):
+ c.quitq(bot,cmd,nick,conn,public)
+def reload(bot,cmd,nick,conn,public):
+ c.reloadq(bot,cmd,nick,conn,public)
+
+#The game...
+class game:
+ trigger="Servus"
+ grace=time.time()
+ minlose=24*60*60 #1 day
+ maxlose=14*minlose #2 weeks
+ losetime=time.time()
+
+# 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),
+ "help": (c.helpq),
+ "trout": (c.troutq,troutcfg),
+ "slash": (c.slashq, slashcfg),
+ "rot13": c.rot13q,
+ "fish": (fishq,fish),
+ "flirt": (c.troutq,flirtcfg),
+ "quiet": (c.nofishq,fish),
+ "reload": reload,
+ "quit": quit,
+ "die": quit,
+ "google": c.googleq,
+ "units": c.unitq,
+# "define": c.defineq,
+# "game": (c.gameq,game),
+ "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.lower()]
+ if callable(e):
+ e(bot,cmd,nick,conn,public)
+ else:
+ e[0](bot,cmd,nick,conn,public,*e[1:])
+ elif public:
+ if cmd.find("GAME")!=-1: #someone else lost
+ grace.grace=time.time()+60*20
+ elif cmd.find(game.trigger)!=-1: #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)
+
--- /dev/null
+# 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 <andrew@lexical.org.uk>
+# Peter Corbett <ptc24@cam.ac.uk>
+# Matthew Vernon <matthew@debian.org>
+# Stephen Early <steve@greenend.org.uk>
+# Richard Kettlewell <rjk@greenend.org.uk
+
+# 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.
+
+import os, time
+
+# 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)
+
+slashcfg= (
+ __load("slashes"),
+ ' (while %s watches)',
+ "Sorry, but %s stole my pen.",
+ "Who do you want to slash?",
+ "I have writer's block!",
+ 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 = "chiark-test-karma"
+# 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
+ savekarma()
+def savekarma():
+ tmp = "%s.tmp" % karmafilename
+ try:
+ f = open(tmp, "w")
+ cPickle.dump(karmadb, f)
+ f.close()
+ os.rename(tmp, karmafilename)
+ except IOError, e:
+ sys.stderr.write("error writing karma: %s" % e)
+
+def quit(bot,cmd,nick,conn,public):
+ c.quitq(bot,cmd,nick,conn,public)
+def reload(bot,cmd,nick,conn,public):
+ c.reloadq(bot,cmd,nick,conn,public)
+
+#The game...
+class game:
+ trigger="Servus"
+ grace=time.time()
+ minlose=24*60*60 #1 day
+ maxlose=14*minlose #2 weeks
+ losetime=time.time()
+
+# 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),
+ "slash": (c.slashq, slashcfg),
+ "rot13": c.rot13q,
+ "fish": (fishq,fish),
+ "flirt": (c.troutq,flirtcfg),
+ "quiet": (c.nofishq,fish),
+ "reload": reload,
+ "quit": quit,
+ "die": quit,
+ "google": c.googleq,
+ "units": c.unitq,
+# "define": c.defineq,
+# "game": (c.gameq,game),
+ "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.lower()]
+ if callable(e):
+ e(bot,cmd,nick,conn,public)
+ else:
+ e[0](bot,cmd,nick,conn,public,*e[1:])
+ elif public:
+ if cmd.find("GAME")!=-1: #someone else lost
+ grace.grace=time.time()+60*20
+ elif cmd.find(game.trigger)!=-1: #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)
+
--- /dev/null
+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
--- /dev/null
+"""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_<type> 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('<URL:type://host/path>') --> '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('<URL:type://host/path>') --> '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 <scheme>_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
+ # '<local>' string by the localhost entry and the corresponding
+ # canonical entry.
+ proxyOverride = proxyOverride.split(';')
+ i = 0
+ while i < len(proxyOverride):
+ if proxyOverride[i] == '<local>':
+ 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()