Initial commit

This commit is contained in:
Neale Pickett 2007-08-24 10:57:29 -06:00
commit 623d1b1506
28 changed files with 4260 additions and 0 deletions

451
#firebot.py# Executable file
View file

@ -0,0 +1,451 @@
#! /usr/bin/env python
import os
import sys
import re
import random
from webretriever import WebRetriever
import asynchat, asyncore
import socket
import csv
import adns
import time
import procbot
import shorturl
import infobot
Runner = procbot.Runner
esc = procbot.esc
URLSERVER = ("", 0)
class SSLSock:
def __init__(self, sock):
self.sock = sock
self.sock.setblocking(1)
self.ssl = socket.ssl(sock)
def send(self, data):
self.ssl.write(data)
def recv(self, bufsize):
return self.ssl.read(bufsize)
def close(self):
self.sock.close()
class FireBot(infobot.InfoBot, procbot.ProcBot):
#debug = True
bindings = []
msg_cat = {}
heartbeat_interval = 0.5
ping_interval = 120
def __init__(self, host, nicks, gecos, channels, dbname='info.db', ssl=False, **kwargs):
infobot.InfoBot.__init__(self, host, nicks, gecos, channels,
**kwargs)
self.ssl = ssl
self.nosy = True
self.seen = {}
def handle_connect(self):
if self.ssl:
self.plain_sock = self.socket
self.socket = SSLSock(self.socket)
infobot.InfoBot.handle_connect(self)
def send_ping(self):
# Send keepalives to the server to see if we've lost
# connection. For some reason, using SSL prevents us from
# getting a RST.
self.write('PING %f' % time.time())
self.add_timer(self.ping_interval,
self.send_ping)
def cmd_001(self, sender, forum, addl):
infobot.InfoBot.cmd_001(self, sender, forum, addl)
self.add_timer(self.ping_interval,
self.send_ping)
def note(self, sender, forum, addl, match):
whom = match.group('whom')
what = match.group('what')
when = time.time()
note = "%f:%s:%s" % (when, sender.name(), what)
n = self.getall(whom, special="note")
n.append(note)
self.set(whom, n, special="note")
forum.msg(self.gettext('okay', sender=sender.name()))
bindings.append((re.compile(r"^\008[:, ]+note (to )?(?P<whom>[^: ]+):? +(?P<what>.*)"),
note))
bindings.append((re.compile(r"^\008[:, ]+later tell (?P<whom>[^: ]+):? +(?P<what>.*)"),
note))
def cmd_privmsg(self, sender, forum, addl):
infobot.InfoBot.cmd_privmsg(self, sender, forum, addl)
if forum.is_channel():
who = sender.name()
# Update seen
text = addl[0]
now = time.time()
self.seen[who] = (now, text)
# Deliver notes
n = self.getall(who, special="note")
if n:
notes = ["Welcome back, %s. You have %d notes:" % (who, len(n))]
for note in n:
when, whom, what = note.split(':', 2)
try:
notes.append(u"%s: %s <%s> %s" % (who,
time.ctime(float(when)),
whom,
what))
except UnicodeDecodeError:
notes.append(u"%s" % ((who,
time.ctime(note[0]),
note[1],
note[2]),))
self.despool(forum, notes)
self.delete(who, special="note")
##
## Firebot stuff
##
def seen(self, sender, forum, addl, match):
whom = match.group('whom')
if whom == sender.name():
forum.msg('Cute, %s.' % whom)
return
last = self.seen.get(whom)
now = time.time()
if last:
when = now - last[0]
units = 'seconds'
if when > 120:
when /= 60
units = 'minutes'
if when > 120:
when /= 60
units = 'hours'
if when > 48:
when /= 24
units = 'days'
forum.msg('I last saw %s %d %s ago, saying "%s"' %
(whom, when, units, last[1]))
else:
forum.msg("I've never seen %s!" % (whom))
bindings.append((re.compile(r"^seen +(?P<whom>.*)$"),
seen))
def evalstr(self, sender, forum, addl, match):
code = match.group('code')
if code in (')', '-)'):
return True
try:
ret = repr(eval(code, {"__builtins__": {}}, {}))
if len(ret) > 400:
ret = ret[:400] + '\026...\026'
except:
t, v, tb = sys.exc_info()
forum.msg(self.gettext('eval', code=code, ret='\002%s\002: %s' % (t, v), sender=sender.name()))
else:
forum.msg(self.gettext('eval', code=code, ret=ret, sender=sender.name()))
#bindings.append((re.compile(r"^\; *(?P<code>.+)$"), evalstr))
#msg_cat['eval'] = ('%(code)s ==> %(ret)s',)
def shorturl(self, sender, forum, addl, match):
url = match.group('url')
print ('url', url)
idx = shorturl.add(url)
forum.msg('http://%s:%d/%d' % (URLSERVER[0], URLSERVER[1], idx))
bindings.append((re.compile(r".*\b(?P<url>\b[a-z]+://[-a-z0-9_=!?#$@~%&*+/:;.,\w]+[-a-z0-9_=#$@~%&*+/\w])"),
shorturl))
def cdecl(self, sender, forum, addl, match):
jibberish = match.group('jibberish')
o, i = os.popen2('/usr/bin/cdecl')
o.write(jibberish + '\n')
o.close()
res = i.read().strip()
if '\n' in res:
forum.msg("Lots of output, sending in private message")
self.despool(sender, res.split('\n'))
else:
forum.msg('cdecl | %s' % res)
bindings.append((re.compile(r"^cdecl (?P<jibberish>.*)$"),
cdecl))
def delayed_say(self, sender, forum, addl, match):
delay = int(match.group('delay'))
unit = match.group('unit')
what = match.group('what')
if not unit or unit[0] == 's':
pass
elif unit[0] == 'm':
delay *= 60
elif unit[0] == 'h':
delay *= 3600
elif unit[0] == 'd':
delay *= 86400
elif unit[0] == 'w':
delay *= 604800
else:
forum.msg("I don't know what a %s is." % unit)
return
self.add_timer(delay, lambda : forum.msg(what))
forum.msg(self.gettext('okay', sender=sender.name()))
bindings.append((re.compile(r"^\008[:, ]+in (?P<delay>[0-9]+) ?(?P<unit>[a-z]*) say (?P<what>.*)"),
delayed_say))
msg_cat['nodict'] = ("Sorry, boss, dict returns no lines for %(jibberish)s",)
def dict(self, sender, forum, addl, match):
jibberish = match.group('jibberish')
i = os.popen('/usr/bin/dict %s 2>&1' % esc(jibberish))
res = i.readlines()
if not res:
forum.msg(self.gettext('nodict', jibberish=jibberish))
return
res = [l.strip() for l in res]
if match.group('long'):
self.despool(sender, res)
else:
if len(res) <= 5:
self.despool(forum, res)
else:
del res[:4]
short = res[:]
while short and ((not short[0]) or (short[0][0] not in '0123456789')):
del short[0]
if not short:
short = res
short = ['%s: %s' % (jibberish, r) for r in short[:4]]
self.despool(forum, short + ['[truncated: use the --long option to see it all]'])
bindings.append((re.compile(r"^dict (?P<long>--?l(ong)? +)?(?P<jibberish>.*)$"),
dict))
def units(self, sender, forum, addl, match):
f = match.group('from')
t = match.group('to')
if f.startswith('a '):
f = '1 ' + f[2:]
Runner('/usr/bin/units -v %s %s' % (esc(f), esc(t)),
lambda l,r: self.proc_cb(None, sender, forum, l, r))
bindings.append((re.compile(r"^units +(?P<from>.*) +in +(?P<to>.*)$"),
units))
bindings.append((re.compile(r"^how many (?P<to>.*) in (?P<from>[^?]*)[?.!]*$"),
units))
def calc(self, sender, forum, addl, match):
e = match.group('expr')
Runner("echo %s | /usr/bin/bc -l" % procbot.esc(e),
lambda l,r: self.proc_cb('%s = ' % e, sender, forum, l, r))
bindings.append((re.compile(r"^(?P<expr>[0-9.]+\s*[-+*/^%]\s*[0-9.]+)$"),
calc))
bindings.append((re.compile(r"^calc (?P<expr>.+)$"),
calc))
def generic_cmd(self, sender, forum, addl, match):
cmd = match.group('cmd')
args = match.group('args').split(' ')
argstr = ' '.join(procbot.lesc(args))
Runner('%s %s' % (cmd, argstr),
lambda l,r: self.proc_cb(None, sender, forum, l, r))
bindings.append((re.compile(r"^(?P<cmd>host) (?P<args>.+)$"),
generic_cmd))
bindings.append((re.compile(r"^(?P<cmd>whois) (?P<args>.+)$"),
generic_cmd))
def pollen(self, sender, forum, addl, match):
forecast_re = re.compile('fimages/std/(?P<count>[0-9]+\.[0-9])\.gif')
predom_re = re.compile('Predominant pollen: (?P<pollens>[^<]*)')
zip = match.group('zip')
def cb(lines):
forecast = []
predom = ''
for line in lines:
match = forecast_re.search(line)
if match:
forecast.append(match.group('count'))
match = predom_re.search(line)
if match:
predom = match.group('pollens')
forum.msg('%s: 4-day forecast (out of 12.0): %s; predominant pollen: %s' %
(zip, ', '.join(forecast), predom))
WebRetriever('http://www.pollen.com/forecast.asp?PostalCode=%s&Logon=Enter' % zip,
cb)
bindings.append((re.compile('pollen (?P<zip>[0-9]{5})'),
pollen))
bindings.append((re.compile('weather (?P<zip>[0-9]{5})'),
weather))
def quote(self, sender, forum, addl, match):
def cb(lines):
if not lines:
forum.msg('oops, no data from server')
return
c = csv.reader([lines[0].strip()])
vals = zip(('symbol', 'value', 'day', 'time', 'change',
'open', 'high', 'low', 'volume',
'market cap', 'previous close',
'percent change', 'open2', 'range',
'eps', 'pe_ratio', 'name'),
c.next())
d = dict(vals)
forum.msg(('%(name)s (%(symbol)s)'
' last:%(value)s@%(time)s'
' vol:%(volume)s'
' cap:%(market cap)s'
' prev-close:%(previous close)s'
' chg:%(change)s(%(percent change)s)'
' open:%(open)s'
' 1d:%(low)s - %(high)s'
' 52wk:%(range)s') %
d)
symbol = match.group('symbol')
WebRetriever('http://quote.yahoo.com/d/quotes.csv?s=%s&f=sl1d1t1c1ohgvj1pp2owern&e=.csv' % symbol,
cb)
bindings.append((re.compile(r"^quote +(?P<symbol>[.a-zA-Z]+)$"),
quote))
def currency(self, sender, forum, addl, match):
amt = float(match.group('amt'))
frm = match.group('from')
to = match.group('to')
def cb(lines):
if not lines:
forum.msg('oops, no data from server')
return
c = csv.reader([lines[0].strip()])
vals = zip(('symbol', 'value', 'day', 'time', 'change',
'open', 'high', 'low', 'volume',
'market cap', 'previous close',
'percent change', 'open2', 'range',
'eps', 'pe_ratio', 'name'),
c.next())
d = dict(vals)
v = float(d['value'])
ans = v * amt
forum.msg(('%0.4f %s = %0.4f %s') %
(amt, frm, ans, to))
WebRetriever(('http://quote.yahoo.com/d/quotes.csv?s=%s%s%%3DX&f=sl1d1t1c1ohgvj1pp2owern&e=.csv' %
(frm, to)),
cb)
bindings.append((re.compile(r"^how much is (?P<amt>[0-9.]+) ?(?P<from>[A-Z]{3}) in (?P<to>[A-Z]{3})\??$"),
currency))
def whuffie_mod(self, nick, amt):
vs = self.get(nick, "0", special="whuffie")
try:
val = int(vs)
except:
val = 0
val += amt
self.set(nick, [str(val)], special="whuffie")
def whuffie_modify(self, sender, forum, addl, match):
nick = match.group('nick')
if nick.lower() == sender.name().lower():
forum.msg(self.gettext('whuffie whore', sender=sender.name()))
return
if match.group('mod') == '++':
amt = 1
else:
amt = -1
self.whuffie_mod(nick, amt)
bindings.append((re.compile(r"^(?P<nick>\w+)(?P<mod>\+\+|\-\-)[? ]*$"),
whuffie_modify))
msg_cat['whuffie whore'] = ("Nothing happens.",
'A hollow voice says, "Fool."')
def whuffie(self, sender, forum, addl, match):
nick = match.group('nick')
val = self.get(nick, special="whuffie")
if val and val != "0":
forum.msg("%s has whuffie of %s" % (nick, val))
else:
forum.msg("%s has neutral whuffie" % nick)
bindings.append((re.compile(r"^(\008[,:] +)?([Ww]huffie|[Kk]arma) (for )?(?P<nick>\w+)[? ]*$"),
whuffie))
#
# This is all stuff that should just be stored in the usual manner.
# But I wrote it here before I realized how programmable an Infobot
# really is, so here it stays.
#
msg_cat['8ball'] = ("%(sender)s: Outlook good.",
"%(sender)s: Outlook not so good.",
"%(sender)s: My reply is no.",
"%(sender)s: Don't count on it.",
"%(sender)s: You may rely on it.",
"%(sender)s: Ask again later.",
"%(sender)s: Most likely.",
"%(sender)s: Cannot predict now.",
"%(sender)s: Yes.",
"%(sender)s: Yes, definitely.",
"%(sender)s: Better not tell you now.",
"%(sender)s: It is certain.",
"%(sender)s: Very doubtful.",
"%(sender)s: It is decidedly so.",
"%(sender)s: Concentrate and ask again.",
"%(sender)s: Signs point to yes.",
"%(sender)s: My sources say no.",
"%(sender)s: Without a doubt.",
"%(sender)s: Reply hazy, try again.",
"%(sender)s: As I see it, yes.")
msg_cat['me'] = ('%(sender)s?',
'%(sender)s: Yes?',
'At your service, %(sender)s.',
'May I help you, %(sender)s?')
msg_cat['thanks'] = ('It is my pleasure, %(sender)s.',
'Of course, %(sender)s.',
'I live but to serve, %(sender)s.',
"All in a day's work, %(sender)s.")
bindings.append((re.compile(r"^(magic )?(8|eight ?)-?ball", re.IGNORECASE),
'8ball'))
bindings.append((re.compile(r"^\008\?$", re.IGNORECASE),
'me'))
bindings.append((re.compile(r"^thank(s| you),? *\008", re.IGNORECASE),
'thanks'))
msg_cat.update(infobot.InfoBot.msg_cat)
bindings.extend(infobot.InfoBot.bindings)
if __name__ == "__main__":
import irc
# Short URL server
us = shorturl.start(('', 0))
URLSERVER = (socket.gethostbyaddr(socket.gethostname())[0],
us.getsockname()[1])
NICK = ['hal']
INFO = 'Daisy, Daisy...'
l1 = FireBot(("server1", 6667),
NICK,
INFO,
["#ch1", "#ch2"])
l2 = FireBot(('server2', 6667),
NICK,
INFO,
["#ch3"])
l1.set_others([l2])
l2.set_others([l1])
irc.run_forever(0.5)

159
README Normal file
View file

@ -0,0 +1,159 @@
== Firebot ==
FireBot is a winner!
Firebot is an IRC bot combining the functionality of a Linkbot, an
Infobot, and a Clickolinko, which is this cool thing Emad El-Haraty and
I came up with to make short URLs out of stuff posted into the channel,
for people with text browsers that wrap URLs.
Note that, in addition to interacting with FireBot within a channel, you
can also communicate directly with FireBot via `/msg` commands. Just in
case you need a little one-on-one action and don't want spew your
playtime around some channel with other folks watching. That can be *so*
distracting. Some commands still require you to preface them with
FireBot's name. Example:
/msg firebot firebot: literal ...
Downloading
-----------
darcs get http://woozle.org/~neale/repos/firebot
LinkBot Features
----------------
Firebot can link channels across networks. It is present in all
channels and the same functions can be accessed on either side.
Everything said on one channel is relayed to the others.
It is possible to link multiple channels on multiple servers, including
multiple channels on a single server.
ClickLinko (UrlBot)
-------------------
Whenever FireBot sees a URL in the channel, he makes a note of it and
creates a shorter URL out of it.
InfoBot
-------
As an InfoBot, FireBot listens in the channel for anything of the form
"x is y", and then stores that little tidbit. Later, when someone asks
a question about x ("what is x?", "who is x?", "wtf is x?"), FireBot
answers with the factoid he gathered.
<dl>
<dt>firebot, _x_</dt>
<dd>look up a factoid for _x_</dd>
<dt>firebot, _x_ is _y_</dt>
<dd>store _y_ as a factiod about _x_</dd>
<dt>firebot, _x_ is also _y_</dt>
<dd>store _y_ as another factoid about _x_</dd>
<dt>firebot, append _x_ &lt;= _y_</dt>
<dd>store _y_ as another factoid about _x_. You'd use this for things where _x_ has the word "is" in it, or other things that you can't store with the preceding commands.</dd>
<dt>no, firebot, _x_ is _y_</dt>
<dd>store _y_ as the only factoid about _x_, even if _x_ already has factoids</dd>
<dt>firebot, literal _x_</dt>
<dd>display all factoids about _x_</dd>
<dt>firebot, lock _x_</dt>
<dd>do not learn any more factoids about _x_</dd>
<dt>firebot, unlock _x_</dt>
<dd>resume learning factoids about _x_</dd>
<dt>firebot, forget _x_</dt>
<dd>forget all factoids about _x_</dd>
<dt>firebot, forget _x_ from _y_</dt>
<dd>forget a single entry (<em>x</em>) that is listed in _y_; _x_ can be a single word, it does not need to be the whole entry</dd>
</dl>
In addition, the following tricks can be used within factiods:
* Any factoid beginning with `\\` (a backslash) is displayed directly.
That is, instead of saying "<firebot> x is y", FireBot just says
"<firebot> y".
* Any factoid beginning with <code>:</code> (a colon) is
displayed an action. That is, instead of saying "<firebot> x is y",
FireBot says "* firebot y"
* You may put `%(sender)s` in the factoid to print the name of the
person who asked about the factoid (when sent to a user in a private
message, it's the recipient of the message)
Utilities
---------
<dl>
<dt>firebot, later tell _whom_ _what_</dt>
<dd>The next time _whom_ says something in the channel, deliver the message _what_ publically.</dd>
<dt>firebot, in _time_ say _what_</dt>
<dd>after _time_ (eg. "15 seconds", "2 hours", "5 days"), announce _what_ in the channel</dd>
<dt>seen _whom_</dt>
<dd>Report the last thing said in the channel by _whom_, and how long ago it was said.</dd>
<dt>dict _word_</dt>
<dd>look _word_ up in the dictionary</dd>
<dt>quote _symbol_</dt>
<dd>get a stock quote for _symbol_</dd>
<dt>pollen _zip_</dt>
<dd>report pollen forecast for US zip code _zip_</dd>
<dt>cdecl explain _jibberish_</dt>
<dd>explain the C declaration _jibberish_ (eg. "cdecl explain struct bar *(*foo)[](int)")</dd>
<dt>cdecl declare _english_</dt>
<dd>give the C declaration for _english_ (eg. "cdecl declare foo as pointer to array of function (int) returning pointer to struct bar")</dd>
<dt>how many _x_ in _y_ _z_</dt>
<dd>determine the number of _x_ items that are contained in _y_ amount of _z_ items (eg. how many miles in 1 light year)</dd>
<dt>how much is _amt_ _source_ in _dest_</dt>
<dd>do a currency conversion from _source_ to _dest_. Both must be three-letter currency codes. (eg. "how much is 100 USD in EUR")</dd>
<dt>calc _expr_</dt>
<dd>calculate _expr_ (eg. "calc 2 * 5")</dd>
</dl>
Toys
----
<dl>
<dt>8ball, _question_</dt>
<dd>consult the magic 8-ball regarding _question_</dd>
<dt>_nickname_++</dt>
<dd>add a whuffie point for _nickname_</dd>
<dt>_nickname_--</dt>
<dd>remove a whuffie point for _nickname_</dd>
<dt>whuffie for _nickname_</dt>
<dd>check the whuffie for _nickname_</dd>
</dl>

66
acrobot.py Executable file
View file

@ -0,0 +1,66 @@
#! /usr/bin/env python
import irc
import random
SERVER = ('woozle.org', 6667)
NAMES = ['acrobot']
INFO = "Acrophobia!"
CHANNELS = ["#acro"]
LETTERS = (
'A' * 176 +
'B' * 167 +
'C' * 251 +
'D' * 136 +
'E' * 104 +
'F' * 101 +
'G' * 91 +
'H' * 107 +
'I' * 105 +
'J' * 30 +
'K' * 30 +
'L' * 89 +
'M' * 146 +
'N' * 53 +
'O' * 50 +
'P' * 195 +
'Q' * 13 +
'R' * 103 +
'S' * 273 +
'T' * 132 +
'U' * 20 +
'V' * 41 +
'W' * 71 +
'X' * 1 +
'Y' * 11 +
'Z' * 6)
class AcroBot(irc.Bot):
def cmd_privmsg(self, sender, forum, addl):
if forum.name() in self.channels:
return
self.command(sender, addl)
def command(self, sender, addl):
print (sender, addl)
def _make_acro(self, min, max):
letters = []
for i in range(random.randint(min, max)):
letters.append(random.choice(LETTERS))
return letters
def cmd_join(self, sender, forum, addl):
self.debug = True
if sender.name() in self.nicks:
self.heartbeat()
def heartbeat(self):
if True:
acro = ''.join(self._make_acro(3, 8))
self.announce(acro)
l2 = AcroBot(SERVER, NAMES, INFO, CHANNELS)
irc.run_forever()

29
addfacts.py Executable file
View file

@ -0,0 +1,29 @@
#! /usr/bin/env python
import urllib2
import shelve
import sys
import InfoBot
def main():
db = shelve.DbfilenameShelf('info.db')
count = 0
for url in sys.argv[1:]:
print url
f = urllib2.urlopen(url)
while True:
line = f.readline()
if not line:
break
line = line.strip()
try:
key, val = line.split(' => ', 1)
except ValueError:
continue
db[key] = (InfoBot.locked, val)
count += 1
print "Added %d facts." % count
if __name__ == '__main__':
main()

140
arsenic.py Executable file
View file

@ -0,0 +1,140 @@
#! /usr/bin/env python
import firebot
from finger import Finger
from procbot import ProcBot, Runner
import shorturl
import asyncore
import irc
import re
import os
import time
import socket
class Arsenic(firebot.FireBot, ProcBot):
debug = True
bindings = []
ping_interval = 120
def __init__(self, *args, **kwargs):
firebot.FireBot.__init__(self, *args, **kwargs)
self.seen = {}
self.lusers = {}
self.heartbeat_interval=3
self.lag = 0
self.whinecount = 0
def rp(self, sender, forum, addl, match):
command = 'rp'
argstr = match.group('args')
Finger(('db.nic.lanl.gov', 5833),
argstr,
lambda l: self.proc_cb('%s: ' % command, sender, forum, l, 0))
bindings.append((re.compile(r"^(?P<command>rp) +(?P<args>.*)$"),
rp))
def finger(self, sender, forum, addl, match):
command = 'finger'
argstr = match.group('args')
Finger(('finger.lanl.gov', 79),
argstr,
lambda l: self.proc_cb('%s: ' % command, sender, forum, l, 0))
bindings.append((re.compile(r"^(?P<command>finger) +(?P<args>.*)$"),
finger))
def lag(self, sender, forum, addl, match):
forum.msg("My server lag is %.3f seconds." % self.lag)
bindings.append((re.compile(r"^\008[,: ]+ (what is the )?(server )?lag"),
lag))
def note(self, sender, forum, addl, match):
whom = match.group('whom')
what = match.group('what')
when = time.time()
key = '\013notes:%s' % whom
print key
note = (when, sender.name(), what)
try:
n = self.db[key]
except KeyError:
n = []
n.append(note)
self.db[key] = n
forum.msg(self.gettext('okay', sender=sender.name()))
bindings.append((re.compile(r"^\008[:, ]+note (to )?(?P<whom>[^: ]+):? +(?P<what>.*)"),
note))
bindings.extend(firebot.FireBot.bindings)
##
## IRC protocol-level extensions
##
def add_luser(self, luser, channel):
# Keeps track of what users have been on what channels, and
# sends an invite to luser for every channel in which they're
# listed. If they're already in the channel, the server just
# sends back an error. This has the effect of letting people
# get back into invite-only channels after a disconnect.
who = luser.name()
self.lusers[channel.name()][who] = luser
for chan in self.lusers.keys():
if chan == channel.name():
continue
t = self.lusers[chan].get(who)
if t and t.host == luser.host:
self.write('INVITE %s %s' % (who, chan))
def cmd_join(self, sender, forum, addl):
if sender.name() == self.nick:
# If it was me, get a channel listing and beg for ops
self.write('WHO %s' % (forum.name()))
forum.notice('If you op me, I will op everyone who joins this channel.')
self.lusers[forum.name()] = {}
else:
# Otherwise, add the user
self.add_luser(sender, forum)
forum.write(['MODE', forum.name(), '+o'], sender.name())
def cmd_352(self, sender, forum, addl):
# Response to WHO
forum = irc.Channel(self, addl[0])
who = irc.User(self, addl[4], addl[1], addl[2])
self.add_luser(who, forum)
def cmd_invite(self, sender, forum, addl):
# Join any channel to which we're invited
self.write('JOIN', forum.name())
def cmd_pong(self, sender, forum, addl):
now = time.time()
print now
self.lag = now - float(addl[0])
def cmd_482(self, sender, forum, addl):
self.whinecount += 1
if (self.whinecount == 2 or
self.whinecount == 4 or
self.whinecount == 8):
forum.notice("Just a reminder: I can't op anyone unless I'm opped myself.")
elif (self.whinecount == 16):
forum.notice("This is the last time I'm going to beg for ops. Puh-leaze?")
if __name__ == '__main__':
# Short URL server
us = shorturl.start(('', 0))
firebot.URLSERVER = (socket.gethostbyaddr(socket.gethostname())[0],
us.getsockname()[1])
NICK = ['arsenic']
INFO = "I'm a little printf, short and stdout"
l1 = Arsenic(('greywolf.lanl.gov', 6697),
NICK,
INFO,
["#x"],
ssl=True)
irc.run_forever()

69
async_proc.py Executable file
View file

@ -0,0 +1,69 @@
#! /usr/bin/env python
"""An asyncore process object.
You'd use it with popen. See the code at the bottom of
this file for an example.
"""
import asyncore
import fcntl
import os
class process_wrapper:
"""A wrapper to make a process look like a socket.
asyncore wants things to look like sockets. So we fake it.
"""
def __init__(self, inf):
self.inf = inf
self.fd = inf.fileno()
def recv(self, size):
return self.inf.read(size)
def send(self, data):
return
def close(self):
return self.inf.close()
def fileno(self):
return self.fd
class process_dispatcher(asyncore.dispatcher):
def __init__(self, inf=None):
asyncore.dispatcher.__init__(self)
self.connected = 1
if inf:
flags = fcntl.fcntl(inf.fileno(), fcntl.F_GETFL, 0)
flags = flags | os.O_NONBLOCK
fcntl.fcntl(inf.fileno(), fcntl.F_SETFL, flags)
self.set_file(inf)
def set_file(self, inf):
self.socket = process_wrapper(inf)
self._fileno = self.socket.fileno()
self.add_channel()
def writable(self):
# It's a one-way socket
return False
if __name__ == '__main__':
class foo(process_dispatcher):
def handle_read(self):
r = self.recv(1024)
if r:
print '[' + r + ']'
def handle_close(self):
print "returned", self.close()
f = os.popen('ls', 'r')
p = foo(f)
asyncore.loop()

114
bindingsbot.py Normal file
View file

@ -0,0 +1,114 @@
import irc
import re
import random
import types
class Match:
"""A wrapper around a regex match, to replace \008 with a word.
"""
def __init__(self, m, txt):
self.m = m
self.txt = txt
def group(self, grp):
g = self.m.group(grp)
if g:
g = g.replace('\008', self.txt)
return g
class BindingsBot(irc.Bot):
"""An IRC bot with regex function bindings
You can bind functions to things said in the channel by regular
expression with this. See wouldmatch for an example of how to do
this.
"""
msg_cat = {} # message catalog
bindings = [] # function/catalog bindings to regexen
def __init__(self, *gar):
irc.Bot.__init__(self, *gar)
self.last_tb = "Nothing's gone wrong yet!"
def err(self, exception):
"""Save the traceback for later inspection"""
irc.Bot.err(self, exception)
t,v,tb = exception
tbinfo = []
while 1:
tbinfo.append ((
tb.tb_frame.f_code.co_filename,
tb.tb_frame.f_code.co_name,
str(tb.tb_lineno)
))
tb = tb.tb_next
if not tb:
break
# just to be safe
del tb
file, function, line = tbinfo[-1]
info = '[' + '] ['.join(map(lambda x: '|'.join(x), tbinfo)) + ']'
self.last_tb = '%s %s %s' % (t, v, info)
print self.last_tb
def matches(self, text):
matches = []
btext = text.replace(self.nick, '\008')
for b in self.bindings:
m = b[0].match(btext)
if m:
matches.append((m, b))
return matches
def cmd_privmsg(self, sender, forum, addl):
for m, b in self.matches(addl[0]):
f = b[1]
if callable(f):
cont = f(self, sender, forum, addl, Match(m, self.nick))
elif type(f) == types.StringType:
forum.msg(self.gettext(f, sender=sender.name(),
forum=forum.name(), me=self.nick))
cont = False
else:
raise ValueError("Can't handle type of %s", `f`)
if not cont:
break
def gettext(self, msg, **dict):
"""Format a message from the message catalog.
Retrieve from the message catalog the message specified by msg,
filling in arguments as specified by dict.
"""
m = random.choice(self.msg_cat[msg])
return m % dict
def tbinfo(self, sender, forum, addl, match):
forum.msg(self.last_tb)
bindings.append((re.compile(r"^\008[,: ]+(tbinfo|traceback)$"),
tbinfo))
def wouldmatch(self, sender, forum, addl, match):
"""Show what binding would be matched"""
text = match.group(1)
matches = self.matches(text)
m = [i[1][1] for i in matches]
forum.msg('%s => %s' % (`text`, `m`))
bindings.append((re.compile(r"^\008[,: ]+match (.+)$"),
wouldmatch))
#
# Message catalog
#
msg_cat['okay'] = ('Okay, %(sender)s.',)

44
convert.py Executable file
View file

@ -0,0 +1,44 @@
#! /usr/bin/env python
import seedyb
import shelve
import cPickle as pickle
import codecs
def main():
a = shelve.open('new.db')
d = seedyb.open('info.cdb')
dec = codecs.getdecoder('utf-8')
enc = codecs.getencoder('utf-8')
for k,l in a.iteritems():
try:
tl = type(l)
if tl == type(13) and k[0] == '\x0b':
# Whuffie
k = k[1:]
d.set(k, str(l), special='whuffie')
elif tl == type(()):
locked = False
try:
k = dec(k)[0]
except UnicodeDecodeError:
continue
# Factoid
if l and l[0] == ('locked',):
locked = True
l = l[1:]
try:
d.set(k, l)
except UnicodeDecodeError:
continue
if locked:
d.lock(k)
except:
print (k, l)
raise
d.sync()
main()

40
daemon.py Executable file
View file

@ -0,0 +1,40 @@
#! /usr/bin/env python
import os
import sys
def daemon(pidfile=None, stdout=None, stderr=None):
# Do this first so errors print out right away
if pidfile:
f = file(pidfile, 'w')
else:
f = None
pid = os.fork()
if pid:
# Exit first parent
os._exit(0)
# Decouple from parent
os.setsid()
# Second fork
pid = os.fork()
if pid:
# Exit second parent
os._exit(0)
# Remap std files
os.close(0)
if stdout:
sys.stdout = stdout
os.close(1)
if stderr:
sys.stderr = stderr
os.close(2)
# Write pid
if f:
f.write(str(os.getpid()))
f.close()

9
dbdump.py Executable file
View file

@ -0,0 +1,9 @@
#! /usr/bin/env python
import anydbm
d = anydbm.open('info.db')
n = anydbm.open('new.db', 'c')
for k,v in d.iteritems():
n[k] = v

63
dorkbot.py Executable file
View file

@ -0,0 +1,63 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-
import firebot
import irc
import re
import os
import random
class Gallium(firebot.FireBot):
bindings = []
bindings.extend(firebot.FireBot.bindings)
def __init__(self, *args, **kwargs):
firebot.FireBot.__init__(self, *args, **kwargs)
self.heartbeat_interval=3
self.debug = True
def randglyph(self, sender, forum, addl, match):
count = 0
while count < 5:
i = random.randint(0, 0xffff)
r = self.get('U+%x' % i)
if r:
forum.msg('U+%X %s' % (i, r))
return
count += 1
forum.msg("I tried %d random numbers and none of them was defined." % count)
bindings.append((re.compile(r"^u\+rand$"),
randglyph))
def whuffie_up(self, sender, forum, addl, match):
nick = match.group('nick')
if nick.lower() == sender.name().lower():
forum.msg(self.gettext('whuffie whore', sender=sender.name()))
return
if match.group('dir') == 'up':
amt = 1
else:
amt = -1
self.whuffie_mod(nick, amt)
bindings.append((re.compile(r"^,(?P<dir>up|down)\s+(?P<nick>\w+)$"),
whuffie_up))
if __name__ == '__main__':
import shorturl
import socket
# Short URL server
us = shorturl.start(('', 0))
firebot.URLSERVER = (socket.gethostbyaddr(socket.gethostname())[0],
us.getsockname()[1])
snowbot = Gallium(('irc.freenode.net', 6667),
['dorkbot'],
"I'm a little printf, short and stdout",
["#rcirc"],
dbname='dorkbot.db')
irc.run_forever(0.5)

381
filedbm.py Executable file
View file

@ -0,0 +1,381 @@
#! /usr/bin/env python
import os
import string
class error(Exception):
pass
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 quote(s, safe):
"""quote('abc def') -> 'abc%20def'."""
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)
class FileDBM:
"""File Database class.
This stores strings as files in a directory.
Note, no locking is done. It would be wise to make sure there is
only one writer at any given time.
"""
safe = string.letters + string.digits + ',!@#$^()-_+='
def __init__(self, base, mode='r'):
self.base = os.path.abspath(base)
if mode in ('r', 'w'):
if not os.path.isdir(base):
raise error("need 'c' or 'n' flag to open new db")
if mode == 'r':
self.writable = True
else:
self.writable = False
elif mode == 'c':
if not os.path.isdir(base):
os.mkdir(base)
self.writable = True
elif mode == 'n':
if os.path.isdir(base):
os.removedirs(base)
os.mkdir(base)
self.writable = True
else:
raise error("flags should be one of 'r', 'w', 'c', or 'n'")
def key2path(self, key):
"""Transform key to a pathname.
By default this does URL quoting on safe characters.
Be sure to provide a path2key method if you override this.
"""
return os.path.join(self.base,
quote(key, self.safe))
def path2key(self, path):
"""Transform a pathname to a key."""
if not path.startswith(self.base):
raise error("Not a valid path")
key = path[len(self.base) + 1:] # +1 gets the /
if os.path.sep in key:
raise error("Not a valid path")
return unquote(key)
def __len__(self):
count = 0
for i in self.iterkeys():
count += 1
return count
def __getitem__(self, key):
if not (type(key) == type('')):
raise TypeError("keys must be strings")
path = self.key2path(key)
try:
return file(path).read()
except IOError:
raise KeyError
def get(self, key, default=None):
try:
return self[key]
except KeyError:
return default
def __setitem__(self, key, val):
if not (type(key) == type(val) == type('')):
raise TypeError("keys and values must be strings")
path = self.key2path(key)
file(path, 'w').write(val)
def setdefault(self, key, default):
try:
return self[key]
except KeyError:
self[key] = default
return default
def __delitem__(self, key):
path = self.key2path(key)
try:
os.remove(path)
except OSError:
raise KeyError()
def __contains__(self, value):
# This could be a lot slower than the user would expect. If you
# need it, use has_value. Of course, you could make a derived
# class that sets __contains__ = has_value
raise error("You didn't really want to do this.")
def has_key(self, key):
return os.path.exists(self.key2path(key))
def has_value(self, value):
for val in self.itervalues():
if val == value:
return True
return False
def iterkeys(self):
for root, dirs, files in os.walk(self.base):
for f in files:
path = os.path.join(root, f)
try:
yield self.path2key(path)
except error:
pass
def __iter__(self):
return self.iterkeys()
def itervalues(self):
for key, val in self.itervalues():
yield val
def iteritems(self):
for k in self.iterkeys():
yield (k, self[k])
def keys(self):
keys = []
for k in self.iterkeys():
keys.append(k)
return keys
def items(self):
items = []
for i in self.iteritems():
items.append(i)
return items
def values(self):
values = []
for v in self.itervalues():
values.append(v)
return values
class LongFileDBM(FileDBM):
"""A file database supporting any-length keys.
It does this by splitting keys up into directories.
"""
# A special string to append to directories, so that no file will
# ever have the same path as a directory
dirsuffix = '%%'
# In the worst case, quote makes the string 3x bigger.
# So any key longer than 80 characters gets split up. This
# gives us plenty of room with a 255-character filename limit,
# which seems to be the minimum limit on any OS these days.
dirlen = 80
def split(self, key):
"""Split a key into its path components.
Each component in the list returned will be a directory. Called
before quoting parts.
This is probably what you want to override. You may need to do
join() too.
"""
parts = []
while key:
parts.append(key[:self.dirlen])
key = key[self.dirlen:]
return parts
def join(self, parts):
"""Join directory parts into a single string.
This is called after unquoting parts.
"""
return ''.join(parts)
def key2path(self, key, makedirs=False):
parts = self.split(key)
path = self.base
for part in parts[:-1]:
# Escape the part
d = quote(part, self.safe)
# Append a safe string so no shorter key can have this
# path
d = d + self.dirsuffix
# Stick it on the end
path = os.path.join(path, d)
# Make directory if requested
if makedirs and not os.path.isdir(path):
os.mkdir(path)
# Now we can add the filename
path = os.path.join(path, quote(parts[-1], self.safe))
return path
def path2key(self, path):
"""Transform a pathname to a key."""
if not path.startswith(self.base):
raise error("Not a valid path")
key = ""
parts = path[len(self.base) + 1:].split(os.path.sep)
parts_ = []
for p in parts:
# Strip the special string
if p.endswith(self.dirsuffix):
p = p[:-len(self.dirsuffix)]
parts_.append(unquote(p))
key = self.join(parts_)
return key
def __setitem__(self, key, val):
if not self.writable:
raise IOError('database was not opened writable')
if not (type(key) == type(val) == type('')):
raise TypeError("keys and values must be strings")
path = self.key2path(key, True)
file(path, 'w').write(val)
def __delitem__(self, key):
path = self.key2path(key)
try:
os.remove(path)
except OSError:
raise KeyError()
# Now try to clean up any directories
while True:
path = os.path.dirname(path)
if len(path) <= len(self.base):
break
try:
os.rmdir(path)
except OSError:
# Guess it's not empty
break
def iterkeys(self):
for root, dirs, files in os.walk(self.base):
for f in files:
path = os.path.join(root, f)
try:
yield self.path2key(path)
except error:
pass
class WordFileDBM(LongFileDBM):
"""A layout using the first word as the top-level directory.
I use this in my firebot, but it's included here more as an example
of how you could extend LongFileDBM.
"""
# I like having spaces in my filenames
safe = LongFileDBM.safe + ' '
def split(self, key):
# Three cases:
#
# 1. no_spaces,_short
# 2. one/one or more spaces
# 3. _long/really_really_really_really_..._long
#
# This means that keys beginning with "_long " will be filed
# with long keys.
#
# In any case, the first directory, if any, can be stripped
# completely.
split = LongFileDBM.split(self, key)
# Split up into words
parts = key.split(' ', 1)
if len(parts) == 1 and len(split) == 1:
# No spaces
return split
elif len(parts[0]) <= self.dirlen:
# >= 2 words, first word <= dirlen chars
return [parts[0]] + split
else:
return ['_long'] + split
def join(self, parts):
# Two cases:
#
# ["one_part"]
# ["more", "more than one part"]
if len(parts) == 1:
return parts[0]
else:
return LongFileDBM.join(self, parts[1:])
open = LongFileDBM
if __name__ == '__main__':
def asserteq(a, b):
assert a == b, "%s != %s" % (`a`, `b`)
f = LongFileDBM('/tmp/db', 'n')
asserteq(f.key2path('this is a thing'), '/tmp/db/this%20is%20a%20thing')
asserteq(f.key2path('1234567890' * 8), '/tmp/db/12345678901234567890123456789012345678901234567890123456789012345678901234567890')
asserteq(f.key2path('1234567890' * 20), '/tmp/db/12345678901234567890123456789012345678901234567890123456789012345678901234567890%%/12345678901234567890123456789012345678901234567890123456789012345678901234567890%%/1234567890123456789012345678901234567890')
f = WordFileDBM('/tmp/db', 'n')
asserteq(f.path2key(f.key2path('this is a thing')), 'this is a thing')
asserteq(f.path2key(f.key2path('1234567890' * 8)), '1234567890' * 8)
asserteq(f.path2key(f.key2path('1234567890' * 20)), '1234567890' * 20)
asserteq(f.get('grape'), None)
asserteq(f.setdefault('grape', 'red'), 'red')
asserteq(f.get('grape'), 'red')
asserteq(f.setdefault('grape', 'green'), 'red')
longstr = '1234567890' * 10
f[longstr] = '1'
asserteq(f[longstr], '1')
asserteq(f.keys(), ['grape', longstr])
del f['grape']
del f[longstr]
asserteq(f.keys(), [])