mirror of https://github.com/nealey/firebot
Initial commit
This commit is contained in:
commit
623d1b1506
|
@ -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)
|
|
@ -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_ <= _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>
|
|
@ -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()
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
|
@ -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()
|
||||||
|
|
|
@ -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.',)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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()
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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(), [])
|
|
@ -0,0 +1,34 @@
|
||||||
|
#! /usr/bin/env python
|
||||||
|
|
||||||
|
import asynchat
|
||||||
|
import socket
|
||||||
|
|
||||||
|
class Finger(asynchat.async_chat):
|
||||||
|
def __init__(self, host, query, callback):
|
||||||
|
asynchat.async_chat.__init__(self)
|
||||||
|
self.query = query
|
||||||
|
self.callback = callback
|
||||||
|
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self.push(self.query + '\n')
|
||||||
|
self.connect(host)
|
||||||
|
self.inbuf = ''
|
||||||
|
self.set_terminator(None)
|
||||||
|
|
||||||
|
def handle_connect(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def collect_incoming_data(self, data):
|
||||||
|
self.inbuf += data
|
||||||
|
|
||||||
|
def handle_close(self):
|
||||||
|
self.callback(self.inbuf)
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import asyncore
|
||||||
|
|
||||||
|
def p(x):
|
||||||
|
print x
|
||||||
|
|
||||||
|
r = finger(('finger.lanl.gov', 79), '121726', p)
|
||||||
|
asyncore.loop()
|
|
@ -0,0 +1,457 @@
|
||||||
|
#! /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))
|
||||||
|
|
||||||
|
def weather(self, sender, forum, addl, match):
|
||||||
|
zip = match.group('zip')
|
||||||
|
def cb(lines):
|
||||||
|
print lines
|
||||||
|
forum.msg('*HURR*')
|
||||||
|
WebRetriever('http://www.srh.noaa.gov/zipcity.php?inputstring=%s' % zip,
|
||||||
|
cb)
|
||||||
|
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)
|
|
@ -0,0 +1,401 @@
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "./gallium.py", line 69, in ?
|
||||||
|
daemon.daemon('gallium.pid', log, log)
|
||||||
|
File "/home/neale/src/firebot/daemon.py", line 38, in daemon
|
||||||
|
f.write(os.getpid())
|
||||||
|
TypeError: argument 1 must be string or read-only character buffer, not int
|
||||||
|
exceptions.KeyError u'user' [/home/neale/src/firebot/irc.py|handle|406] [/home/neale/src/firebot/irc.py|handle_cooked|418] [/home/neale/src/firebot/firebot.py|cmd_privmsg|88] [/home/neale/src/firebot/bindingsbot.py|cmd_privmsg|71] [/home/neale/src/firebot/infobot.py|lookup|210]
|
||||||
|
exceptions.KeyError u'user' [/home/neale/src/firebot/irc.py|handle|406] [/home/neale/src/firebot/irc.py|handle_cooked|418] [/home/neale/src/firebot/firebot.py|cmd_privmsg|88] [/home/neale/src/firebot/bindingsbot.py|cmd_privmsg|71] [/home/neale/src/firebot/infobot.py|lookup|210]
|
||||||
|
exceptions.KeyError u'user' [/home/neale/src/firebot/irc.py|handle|406] [/home/neale/src/firebot/irc.py|handle_cooked|418] [/home/neale/src/firebot/firebot.py|cmd_privmsg|88] [/home/neale/src/firebot/bindingsbot.py|cmd_privmsg|71] [/home/neale/src/firebot/infobot.py|lookup|210]
|
||||||
|
exceptions.KeyError u'user' [/home/neale/src/firebot/irc.py|handle|406] [/home/neale/src/firebot/irc.py|handle_cooked|418] [/home/neale/src/firebot/firebot.py|cmd_privmsg|88] [/home/neale/src/firebot/bindingsbot.py|cmd_privmsg|71] [/home/neale/src/firebot/infobot.py|lookup|210]
|
||||||
|
exceptions.KeyError u'user' [/home/neale/src/firebot/irc.py|handle|406] [/home/neale/src/firebot/irc.py|handle_cooked|418] [/home/neale/src/firebot/firebot.py|cmd_privmsg|88] [/home/neale/src/firebot/bindingsbot.py|cmd_privmsg|71] [/home/neale/src/firebot/infobot.py|lookup|210]
|
||||||
|
exceptions.KeyError u'user' [/home/neale/src/firebot/irc.py|handle|406] [/home/neale/src/firebot/irc.py|handle_cooked|418] [/home/neale/src/firebot/firebot.py|cmd_privmsg|88] [/home/neale/src/firebot/bindingsbot.py|cmd_privmsg|71] [/home/neale/src/firebot/infobot.py|lookup|210]
|
||||||
|
exceptions.KeyError u'user' [/home/neale/src/firebot/irc.py|handle|406] [/home/neale/src/firebot/irc.py|handle_cooked|418] [/home/neale/src/firebot/firebot.py|cmd_privmsg|88] [/home/neale/src/firebot/bindingsbot.py|cmd_privmsg|71] [/home/neale/src/firebot/infobot.py|lookup|210]
|
||||||
|
exceptions.KeyError u'user' [/home/neale/src/firebot/irc.py|handle|406] [/home/neale/src/firebot/irc.py|handle_cooked|418] [/home/neale/src/firebot/firebot.py|cmd_privmsg|88] [/home/neale/src/firebot/bindingsbot.py|cmd_privmsg|71] [/home/neale/src/firebot/infobot.py|lookup|210]
|
||||||
|
exceptions.KeyError u'user' [/home/neale/src/firebot/irc.py|handle|406] [/home/neale/src/firebot/irc.py|handle_cooked|418] [/home/neale/src/firebot/firebot.py|cmd_privmsg|88] [/home/neale/src/firebot/bindingsbot.py|cmd_privmsg|71] [/home/neale/src/firebot/infobot.py|lookup|210]
|
||||||
|
exceptions.KeyError u'user' [/home/neale/src/firebot/irc.py|handle|406] [/home/neale/src/firebot/irc.py|handle_cooked|418] [/home/neale/src/firebot/firebot.py|cmd_privmsg|88] [/home/neale/src/firebot/bindingsbot.py|cmd_privmsg|71] [/home/neale/src/firebot/infobot.py|lookup|210]
|
||||||
|
bsddb._db.DBRunRecoveryError (-30978, 'DB_RUNRECOVERY: Fatal error, run database recovery -- PANIC: Invalid argument') [/home/neale/src/firebot/irc.py|handle|406] [/home/neale/src/firebot/irc.py|handle_cooked|418] [/home/neale/src/firebot/firebot.py|cmd_privmsg|88] [/home/neale/src/firebot/bindingsbot.py|cmd_privmsg|71] [/home/neale/src/firebot/infobot.py|store|285] [/home/neale/src/firebot/infobot.py|do_store|264] [/home/neale/src/firebot/infobot.py|set|83] [/usr/lib/python2.3/shelve.py|__setitem__|130] [/usr/lib/python2.3/bsddb/__init__.py|__setitem__|120]
|
||||||
|
bsddb._db.DBRunRecoveryError (-30978, 'DB_RUNRECOVERY: Fatal error, run database recovery -- PANIC: fatal region error detected; run recovery') [/home/neale/src/firebot/irc.py|handle|406] [/home/neale/src/firebot/irc.py|handle_cooked|418] [/home/neale/src/firebot/firebot.py|cmd_privmsg|88] [/home/neale/src/firebot/bindingsbot.py|cmd_privmsg|71] [/home/neale/src/firebot/infobot.py|lookup|204] [/home/neale/src/firebot/infobot.py|get|71] [/usr/lib/python2.3/shelve.py|__getitem__|118] [/usr/lib/python2.3/bsddb/__init__.py|__getitem__|116]
|
||||||
|
bsddb._db.DBRunRecoveryError (-30978, 'DB_RUNRECOVERY: Fatal error, run database recovery -- PANIC: fatal region error detected; run recovery') [/home/neale/src/firebot/irc.py|handle|406] [/home/nealesync
|
||||||
|
sync
|
||||||
|
exceptions.TypeError can only concatenate list (not "tuple") to list [/home/neale/src/firebot/irc.py|handle|406] [/home/neale/src/firebot/irc.py|handle_cooked|418] [/home/neale/src/firebot/firebot.py|cmd_privmsg|87] [/home/neale/src/firebot/bindingsbot.py|cmd_privmsg|71] [/home/neale/src/firebot/infobot.py|store|263] [/home/neale/src/firebot/infobot.py|do_store|239]
|
||||||
|
exceptions.TypeError can only concatenate list (not "tuple") to list [/home/neale/src/firebot/irc.py|handle|406] [/home/neale/src/firebot/irc.py|handle_cooked|418] [/home/neale/src/firebot/firebot.py|cmd_privmsg|87] [/home/neale/src/firebot/bindingsbot.py|cmd_privmsg|71] [/home/neale/src/firebot/infobot.py|store|263] [/home/neale/src/firebot/infobot.py|do_store|239]
|
||||||
|
exceptions.TypeError can only concatenate list (not "tuple") to list [/home/neale/src/firebot/irc.py|handle|406] [/home/neale/src/firebot/irc.py|handle_cooked|418] [/home/neale/src/firebot/firebot.py|cmd_privmsg|87] [/home/neale/src/firebot/bindingsbot.py|cmd_privmsg|71] [/home/neale/src/firebot/infobot.py|store|263] [/home/neale/src/firebot/infobot.py|do_store|239]
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "./gallium.py", line 84, in ?
|
||||||
|
irc.run_forever(0.5)
|
||||||
|
File "/home/neale/src/firebot/irc.py", line 604, in run_forever
|
||||||
|
asyncore.loop(timeout)
|
||||||
|
File "/usr/lib/python2.3/asyncore.py", line 193, in loop
|
||||||
|
poll_fun(timeout, map)
|
||||||
|
File "/usr/lib/python2.3/asyncore.py", line 108, in poll
|
||||||
|
r, w, e = select.select(r, w, e, timeout)
|
||||||
|
KeyboardInterrupt
|
||||||
|
('url', u'http://www.techcrunch.com/2007/02/07/yahoo-launches-pipes/')
|
||||||
|
('url', u'http://www.games4work.com/games/swf/supercoolpic.swf')
|
||||||
|
('url', u'http://daringfireball.net/2005/09/anthropomorphized')
|
||||||
|
('url', u'http://daringfireball.net/2006/01/brushed_metal')
|
||||||
|
('url', u'http://pomonahistorical.org/12times.htm')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=HKEuzxC4eGc')
|
||||||
|
('url', u'http://it.slashdot.org/article.pl?sid=07/06/11/127253&from=rss')
|
||||||
|
('url', u'http://www.chaos.org.uk/~eddy/img/jest/GodKills.png')
|
||||||
|
('url', u'http://www.80snostalgia.com/')
|
||||||
|
('url', u'http://www.80snostalgia.com/modules.php?op=modload&name=Reviews&file=index&req=showcontent&id=7')
|
||||||
|
('url', u'https://woozle.org/blogs/aim/2007-06-04T03:19:38Z#2007-06-10T19:10:17Z.3055.txt')
|
||||||
|
('url', u'http://www.deviantart.com/deviation/34244097')
|
||||||
|
('url', u'http://rubyonrails.com/')
|
||||||
|
('url', u'http://www.lobotomo.com/products/FanControl/index.html')
|
||||||
|
('url', u'http://www.apple.com/safari/')
|
||||||
|
('url', u'http://www.macports.org/')
|
||||||
|
('url', u'http://www.cs.ubc.ca/spider/harrison/Java/sorting-demo.html')
|
||||||
|
('url', u'http://www.joelonsoftware.com/items/2007/06/11.html')
|
||||||
|
('url', u'http://wakoopa.com/')
|
||||||
|
('url', u'http://java.com/en/')
|
||||||
|
('url', u'http://java.com/en/download/manual.jsp')
|
||||||
|
('url', u'http://www-ai.ijs.si/eliza/eliza.html')
|
||||||
|
('url', u'http://seattlepi.nwsource.com/local/299253_inconvenient11.html')
|
||||||
|
('url', u'http://toastworks.org/jaime')
|
||||||
|
('url', u'http://toastworks.org/Jaime/Welcome.html')
|
||||||
|
('url', u'http://toastworks.org/Jaime/Photos/Photos.html')
|
||||||
|
('url', u'http://www.urbandictionary.com/define.php?term=pagan')
|
||||||
|
('url', u'http://blog.seattlepi.nwsource.com/venture/archives/116630.asp?source=rss')
|
||||||
|
('url', u'http://www.washingtonpost.com/wp-dyn/content/article/2007/06/13/AR2007061301032.html?nav=rss_email/components')
|
||||||
|
('url', u'http://www.flickr.com/photos/hbunny/548567724/')
|
||||||
|
('url', u'http://www.flickr.com/photos/hbunny/548574746/')
|
||||||
|
('url', u'http://www.flickr.com/photos/hbunny/548563718/')
|
||||||
|
('url', u'http://www.customrebreathers.com/COPIS.pdf')
|
||||||
|
('url', u'http://online.wsj.com/public/article/SB118178455437034753-wnZoGVvhvlVHfmGLn3rf6n5FKws_20070714.html?mod=fpa_editors_picks')
|
||||||
|
('url', u'http://woozle.org/~brian/neale.png')
|
||||||
|
('url', u'http://www.techcrunch.com/2007/06/13/ebay-stares-down-google-and-wins/')
|
||||||
|
('url', u'http://www.tubsseattle.com/')
|
||||||
|
('url', u'http://www.king5.com/topstories/stories/NW_061307WABmicrowavepopcornbanTP.43bf29d9.html')
|
||||||
|
('url', u'http://kirkpetersen.s3.amazonaws.com/first')
|
||||||
|
('url', u'http://amazon.rubyforge.org/')
|
||||||
|
('url', u'http://www.omnigroup.com/applications/omnifocus/download/')
|
||||||
|
('url', u'http://www.omnigroup.com/applications/omnifocus/download/sneakypeek/')
|
||||||
|
('url', u'http://hombrelobo.com/taiwan/hombrelobotv-baloncesto-en-taiwan/')
|
||||||
|
('url', u'http://www.guyd2.com/widget/oblique/index.html')
|
||||||
|
('url', u'http://woozle.org/~neale/tmp/foo')
|
||||||
|
('url', u'http://www.lemondbikes.com/bikes/cyclocross/classic_steel/poprad_disc.php')
|
||||||
|
('url', u'http://www.rei.com/product/744803')
|
||||||
|
('url', u'http://www.salsacycles.com/lascruces.html')
|
||||||
|
('url', u'http://tinyurl.com/352725')
|
||||||
|
('url', u'http://www.cnn.com/2007/LAW/06/18/topless.settlement.ap/index.html?eref=rss_topstories')
|
||||||
|
('url', u'http://www.nongnu.org/ratpoison/inspiration.html')
|
||||||
|
('url', u'http://www.asstr.org/')
|
||||||
|
('url', u'http://69lovesongs.info/wiki/')
|
||||||
|
('url', u'http://blog.wired.com/business/search/index.html')
|
||||||
|
('url', u'http://talesfromtheoutback.blogspot.com/')
|
||||||
|
('url', u'http://www.ram-mount.com/aqua_box_mount/aqua_box_mount.htm')
|
||||||
|
('url', u'http://www.ram-mount.com/products/rambigscreengps.htm')
|
||||||
|
('url', u'http://www.viewranger.com/')
|
||||||
|
('url', u'http://www.apple.com/downloads/macosx/')
|
||||||
|
('url', u'http://woozle.org/~neale/images/itsits')
|
||||||
|
('url', u'http://farm1.static.flickr.com/134/331689212_67bd78ce15_o.jpg')
|
||||||
|
('url', u'http://www.boingboing.net/2007/06/19/bicyclists_account_o.html')
|
||||||
|
('url', u'http://www.43folders.com/2007/06/19/buffington-igtd-01/')
|
||||||
|
('url', u'http://www.macuser.com/huh/macbook_deal_of_a_lifetime.php?lsrc=murss')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=D35uQCtr4EY')
|
||||||
|
('url', u'http://www.classicrendezvous.com/Germany/Bauer_City_50anni.htm')
|
||||||
|
('url', u'http://www.rei.com/product/744802?cm_re=LS*SP*Transfer')
|
||||||
|
('url', u'http://london.openguides.org/index.cgi?id=Mend-A-Bike')
|
||||||
|
('url', u'http://www.mend-a-bike.co.uk/')
|
||||||
|
('url', u'http://www.velorution.biz/pret/?page_id=2')
|
||||||
|
('url', u'http://woozle.org/~neale/src/misc/frobnitz.ml')
|
||||||
|
('url', u'http://money.cnn.com/galleries/2007/biz2/0706/gallery.50whomatter.biz2/50.html')
|
||||||
|
('url', u'http://www.macuser.com/humor/your_computer_will_be_a_big_as.php?lsrc=murss')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=d5YrB7TpT1Y')
|
||||||
|
('url', u'http://www.apple.com/iphone/usingiphone/guidedtour_small.html')
|
||||||
|
('url', u'http://www.flickr.com/photos/30708980%40N00/527858009/in/pool-kiltedlife/')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=GcDshWmhF4A')
|
||||||
|
('url', u'http://en.wikipedia.org/wiki/People_eating_tasty_animals')
|
||||||
|
('url', u'http://www.dell.com/content/products/features.aspx/cto_inspn_1501?c=us&cs=19&l=en&s=dhs')
|
||||||
|
('url', u'http://www.timferriss.com/ferriss-book-outsourcing.htm')
|
||||||
|
('url', u'http://www.urbandictionary.com/define.php?term=clam+jousting')
|
||||||
|
('url', u'http://arstechnica.com/news.ars/post/20070621-folksonomy-most-hated-word-on-the-internet.html')
|
||||||
|
('url', u'http://www.emergencybacon.com/')
|
||||||
|
('url', u'http://www.allaboutsymbian.com/reviews/item/Nokia_N95-The_Conclusion.php')
|
||||||
|
('url', u'http://woozle.org/~brian/Highlands001.jpg')
|
||||||
|
('url', u'http://woozle.org/~brian/Highlands009.jpg')
|
||||||
|
('url', u'http://woozle.org/~brian/Highlands057.jpg')
|
||||||
|
('url', u'http://woozle.org/~brian/Highlands010.jpg')
|
||||||
|
('url', u'http://woozle.org/~brian/Highlands120.jpg')
|
||||||
|
('url', u'http://mahieu.org.uk')
|
||||||
|
('url', u'http://seattletimes.nwsource.com/html/nationworld/2003762672_scotus26.html')
|
||||||
|
('url', u'http://www.washingtonpost.com/wp-dyn/content/article/2007/06/25/AR2007062500548.html?hpid%3Dtopnews&sub=new')
|
||||||
|
('url', u'http://www.washingtonpost.com/wp-dyn/content/article/2007/06/25/AR2007062500548.html?hpid%3Dtopnews&sub=new')
|
||||||
|
('url', u'http://www.info.gov.za/documents/constitution/1996/96cons2.htm#16')
|
||||||
|
('url', u'http://www.info.gov('url', u'http://uc.org/read/ZoIP%20Demo')
|
||||||
|
('url', u'http://ec2-72-44-51-133.z-1.compute-1.amazonaws.com/')
|
||||||
|
('url', u'http://ec2-72-44-51-133.z-1.compute-1.amazonaws.com/myapp/public/')
|
||||||
|
('url', u'http://www.engadget.com/2007/06/28/jobs-pulls-an-oprah-12m-in-iphones-for-all-apple-employees/')
|
||||||
|
('url', u'http://www.arielmotor.co.uk/')
|
||||||
|
('url', u'http://www.teslamotors.com/index.php')
|
||||||
|
('url', u'http://www.teslamotors.com/index.php')
|
||||||
|
('url', u'http://www.randsinrepose.com/')
|
||||||
|
('url', u'http://www.amazon.com/Managing-Humans-Humorous-Software-Engineering/dp/159059844X')
|
||||||
|
('url', u'http://www.homestarrunner.com/2manyknives.html')
|
||||||
|
('url', u'http://www.hrwiki.org/index.php/Pistols_For_Pandas')
|
||||||
|
('url', u'http://maps.google.com/maps?f=q&hl=en&geocode=&q=bellevue+tullys&ie=UTF8&ll=47.570388,-122.134495&spn=0.081767,0.14986&z=13&om=1')
|
||||||
|
('url', u'http://www.apple.com/iphone')
|
||||||
|
('url', u'http://googlemapsmania.blogspot.com/2007/06/driving-directions-get-even-better-on.html')
|
||||||
|
('url', u'http://flickr.com/photos/tags/iphone/')
|
||||||
|
('url', u'http://news.bbc.co.uk/1/hi/technology/6255334.stm')
|
||||||
|
('url', u'http://stream.ifixit.com/')
|
||||||
|
('url', u'http://discussions.apple.com/thread.jspa?threadID=1018525&tstart=0')
|
||||||
|
('url', u'http://www.betterphoto.com/gallery/gallery.asp?memberID=232272')
|
||||||
|
('url', u'http://gizmodo.com/gadgets/apple/what-the-iphone-doesnt-have-272571.php')
|
||||||
|
('url', u'http://darlamack.blogs.com/darlamack/2007/06/the-phone-of-th.html')
|
||||||
|
('url', u'http://www.betterphoto.com/gallery/gallery.asp?memberID=232272')
|
||||||
|
('url', u'http://youtube.com/watch?v=yL9pV0TMa2Q')
|
||||||
|
('url', u'http://en.wikipedia.org/wiki/Desmodromic_valve')
|
||||||
|
('url', u'http://en.wikipedia.org/wiki/Pneumatic_valve_gear')
|
||||||
|
('url', u'http://www.7-eleven.com/')
|
||||||
|
('url', u'http://geekbriefwp.podshow.com/')
|
||||||
|
('url', u'http://www.evdaytona.com/')
|
||||||
|
('url', u'http://antwrp.gsfc.nasa.gov/apod/image/0707/pelicanzoom_alves.mov')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=Z1eFdUSnaQM')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=T_tiBGOEoVM')
|
||||||
|
('url', u'http://woozle.org/~neale/tmp/fget')
|
||||||
|
('url', u'http://www.betterphoto.com/gallery/gallery.asp?memberID=232272')
|
||||||
|
('url', u'http://www.camaro.com/wordpress/')
|
||||||
|
('url', u'http://pc.gamespy.com/pc/fallout-3/800771p1.html')
|
||||||
|
('url', u'http://www.ted.com/index.php/talks/view/id/131')
|
||||||
|
('url', u'http://pbfcomics.com/?cid=PBF077AD-Disassemble.jpg#145')
|
||||||
|
('url', u'http://woozle.org/~neale/tmp/spamfairy')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=u-pivHK8DKA')
|
||||||
|
('url', u'http://www.slashfilm.com/2007/06/29/jj-abrams-top-secret-cloverfield-movie-trailer-attached-to-transformers/')
|
||||||
|
('url', u'http://www.theregister.co.uk/2007/07/02/terror_idiocy_outbreak/')
|
||||||
|
('url', u'http://www.macnn.com/articles/07/07/03/iphone.breaks.att.record/')
|
||||||
|
('url', u'http://en.wikipedia.org/wiki/List_of_people_pardoned_by_a_United_States_president')
|
||||||
|
('url', u'http://en.wikipedia.org/wiki/List_of_big-bust_models_and_performers')
|
||||||
|
('url', u'http://woozle.org/~neale/papers/tartans')
|
||||||
|
('url', u'http://www.kathyskilts.com/images/NMTartan.jpg')
|
||||||
|
('url', u'http://en.wikipedia.org/wiki/Royal_Stewart_Tartan')
|
||||||
|
('url', u'http://en.wikipedia.org/wiki/Clan_MacLennan')
|
||||||
|
('url', u'http://woozle.org/~neale/toys/tartan.cgi?t=MacGiggles&s=G16+B8+W2+R2+W2+BK2+G32+BK4+B4+BK2')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=HoXsgF_Pllk')
|
||||||
|
('url', u'http://www.semyan.com/OurVan/Images/DSCN0278.jpg')
|
||||||
|
('url', u'http://www.engadget.com/2007/07/05/man-sticks-jet-engine-in-kayak-somehow-survives/')
|
||||||
|
('url', u'http://lifehacker.com/software/bookmarks/hack-attack-firefox-and-the-art-of-keyword-bookmarking-196779.php')
|
||||||
|
('url', u'http://www.lyricstop.com/c/crazybitch-buckcherry.html')
|
||||||
|
('url', u'http://www.cardomain.com/ride/2346465')
|
||||||
|
('url', u'http://en.wikipedia.org/wiki/Ages_of_consent_in_North_America')
|
||||||
|
('url', u'http://en.wikipedia.org/wiki/List_of_big-bust_models_and_performers')
|
||||||
|
('url', u'http://www.flickr.com/photos/49349060@N00/733218030/in/set-72157600672835782/')
|
||||||
|
('url', u'feed://www.pbs.org/cringely/pulpit/rss2.xml')
|
||||||
|
('url', u'http://en.wikipedia.org/wiki/Image:Ipod_sales.svg')
|
||||||
|
('url', u'http://www.woot.com/Images/Sale/Bissell_Barbie_Real_Vacuum68H-detail.jpg')
|
||||||
|
('url', u'http://img.alibaba.com/photo/50451984/Cyclone_Vacuum_Cleaner.jpg')
|
||||||
|
('url', u'http://woozle.org/~neale/images/viffer.jpg')
|
||||||
|
('url', u'http://kyyhkynen.net/stuff/diggtris/')
|
||||||
|
('url', u'http://www.nps.gov/mora/')
|
||||||
|
('url', u'http://www.nps.gov/mora/planyourvisit/road-status.htm')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=84YWuxeOi9k')
|
||||||
|
('url', u'http://www.collegehumor.com/video:1764124/')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=01lntWJImF8')
|
||||||
|
('url', u'http://www.virulent.org/converg4/past.html')
|
||||||
|
('url', u'http://www.liveleak.com/view?i=a76_1183278403')
|
||||||
|
('url', u'http://www.liveleak.com/view%3Fi%3Da76_1183278403')
|
||||||
|
('url', u'http://www.tuaw.com/2007/07/06/irc-on-iphone/')
|
||||||
|
('url', u'http://en.wikipedia.org/wiki/Jazz_%28Transformers%29')
|
||||||
|
('url', u'http://www.flickr.com/photos/calilewis')
|
||||||
|
('url', u'http://youtube.com/watch?v=PU0VstC82Fg')
|
||||||
|
('url', u'http://www.seattleaquarium.org/NetCommunity/Page.aspx?&pid=375&srcid=183')
|
||||||
|
('url', u'http://www.seattleaquarium.org/NetCommunity/Page.aspx?&pid=183&srcid=-2')
|
||||||
|
('url', u'http://www.twit.tv/mbw44')
|
||||||
|
('url', u'http://www.twit.tv/mbw4')
|
||||||
|
('url', u'http://digg.originalsignal.com/')
|
||||||
|
('url', u'http://www.motorcycledaily.com/08july07_2008buell_1125r.htm')
|
||||||
|
('url', u'http://64.233.179.110/blog_resources/postini_faq.pdf')
|
||||||
|
('url', u'http://www.postini.com/')
|
||||||
|
('url', u'http://code.google.com/p/telekinesis/')
|
||||||
|
('url', u'http://woozle.org/~brian/30sexposure.png')
|
||||||
|
('url', u'http://www.fatsalmonswim.org/')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=qg1ckCkm8YI')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=LZ-3W27f1LM')
|
||||||
|
('url', u'http://undo-software.com/')
|
||||||
|
('url', u'http://en.wikipedia.org/wiki/Paper_size')
|
||||||
|
('url', u'http://sfgate.com/cgi-bin/article.cgi?f=/c/a/2007/07/11/MNGCOQUE0K1.DTL')
|
||||||
|
('url', u'http://www.mnftiu.cc/mnftiu.cc/fighting.002.html')
|
||||||
|
('url', u'http://archives.seattletimes.nwsource.com/cgi-bin/texis.cgi/web/vortex/display?slug=taste11&date=20070711&query=wine+bar+belltown')
|
||||||
|
('url', u'http://en.wikipedia.org/wiki/Captcha')
|
||||||
|
('url', u'http://seattle.craigslist.org/see/sof/371613256.html')
|
||||||
|
('url', u'http://www.theage.com.au/news/national/top-cop-predicts-robot-crimewave/2007/07/06/1183351416078.html')
|
||||||
|
('url', u'http://www.lavanguardia.es/lv24h/20070712/51372978306.html')
|
||||||
|
('url', u'http://www.willitblend.com/videos.aspx?type=unsafe&video=iphone')
|
||||||
|
('url', u'http://edb.seattletimes.nwsource.com/ae/scr/edb_vd.cfm?ven=1843&s=st&Go.x=12&Go.y=14')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=qHO8l-Bd1O4&NR')
|
||||||
|
('url', u'http://seattle.craigslist.org/see/eng/372775024.html')
|
||||||
|
('url', u'http://tinyurl.com/yub592')
|
||||||
|
('url', u'http://youtube.com/watch?v=Qc6w4SzIUN0')
|
||||||
|
('url', u'http://gizmodo.com/gadgets/iphone/iphone-field-test-mode-lets-you-spy-on-the-att-network-277797.php')
|
||||||
|
('url', u'http://img.waffleimages.com/27ab06b31a0a90f378e08e4170684e0d646224b7/awfuck4in.jpg')
|
||||||
|
('url', u'http://sfgate.com/cgi-bin/object/article?f=/n/a/2007/07/13/international/i113736D68.DTL&o=0')
|
||||||
|
('url', u'http://youtube.com/watch?v=Z4VNMERVsC4')
|
||||||
|
('url', u'http://en.wikipedia.org/wiki/SPDIF')
|
||||||
|
('url', u'http://static.flickr.com/80/242725372_3d88afcb4d_b.jpg')
|
||||||
|
('url', u'http://www.gapingvoid.com/')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=ZbbxA8a_M_s')
|
||||||
|
('url', u'http://blog.guykawasaki.com/2007/07/ten-questions-w.html')
|
||||||
|
('url', u'http://thepiratebay.org/')
|
||||||
|
('url', u'http://www.obdev.at/products/launchbar/index.html')
|
||||||
|
('url', u'http://select.nytimes.com/pages/timesselect/index.html')
|
||||||
|
('url', u'http://www.homeworkingsolutions.co.uk/fastaeron.cfm')
|
||||||
|
('url', u'http://www.amazon.com/Bungie-Office-Euro-Style-Furniture/dp/B000BDLFQO')
|
||||||
|
('url', u'http://www.homeworkingsolutions.co.uk/fastcelle/index.cfm')
|
||||||
|
('url', u'http://www.suzukicycles.com/Products/AN400K8/Default.aspx')
|
||||||
|
('url', u'http://powersports.honda.com/scooters/model.asp?ModelName=Metropolitan&ModelYear=2007&ModelId=CHF507')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=efNFFDk3B1A')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=Xs3SfNANtig')
|
||||||
|
('url', u'http://www.iphonehints.com/')
|
||||||
|
('url', u'http://upload.wikimedia.org/wikipedia/en/e/ed/LD_Turtle.jpg')
|
||||||
|
('url', u'http://www.discountlaserdisc.com/read.php?list=3&sort=SCI&sort3=name')
|
||||||
|
('url', u'http://www.netflix.com/HowItWorks?lnkctr=nmhhiw')
|
||||||
|
('url', u'http://trac.xiph.org/ticket/245')
|
||||||
|
('url', u'http://www.theonion.com/content/news/john_edwards_vows_to_end_all_bad')
|
||||||
|
('url', u'http://www.imdb.com/title/tt0827735/')
|
||||||
|
('url', u'http://www.theonion.com/content/news/bar_skanks_announce_plans_to_kiss')
|
||||||
|
('url', u'http://www.theonion.com/content/news/after_5_years_in_u_s_terrorist')
|
||||||
|
('url', u'http://en.wikipedia.org/wiki/BOFH')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=ZGp0hCxSg98')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=54vtXRI32MQ')
|
||||||
|
('url', u'http://tinyurl.com/yub592')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=CZrr7AZ9nCY')
|
||||||
|
('url', u'http://cray.com/products/xmt/index.html')
|
||||||
|
('url', u'http://en.wikipedia.org/wiki/Cray_XMT')
|
||||||
|
('url', u'http://www.drobo.com/')
|
||||||
|
('url', u'http://video.google.com/videoplay?docid=-483097404762739553&hl=en-GB')
|
||||||
|
('url', u'http://www.jroller.com/rolsen/entry/building_a_software_team_five')
|
||||||
|
('url', u'http://video.google.com/videoplay?docid=-483097404762739553&hl=en-')
|
||||||
|
('url', u'http://abcnews.go.com/Travel/wireStory?id=3375212')
|
||||||
|
('url', u'http://www.google.com/search?client=safari&rls=en&q=45+euros+in+dollars&ie=UTF-8&oe=UTF-8')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=IVwIGti0m28')
|
||||||
|
('url', u'http://toastfloats.blogspot.com/2007/03/marine-tax.html')
|
||||||
|
('url', u'http://www.poetv.com/video.php?vid=19841')
|
||||||
|
('url', u'http://yro.slashdot.org/yro/07/07/19/1551242.shtml')
|
||||||
|
('url', u'http://rawstory.com/news/2007/Oldline_Republican_warns_somethings_in_works_0719.html')
|
||||||
|
('url', u'http://rawstory.com/news/2007/Jon_Stewart_Scientific_basis_for_irrational_0718.html')
|
||||||
|
('url', u'http://www.theonion.com/content/node/33576')
|
||||||
|
('url', u'http://www.cnn.com/2007/POLITICS/07/20/bush.colonoscopy/index.html')
|
||||||
|
('url', u'http://www.circuitcity.com/ccd/productDetail.do?oid=116323&linkid=j13774590k9470&affiliateid=k9470&mid=')
|
||||||
|
('url', u'http://uk.reuters.com/article/domesticNews/idUKL1847347220070719?pageNumber=1')
|
||||||
|
('url', u'http://leo.tumblr.com/post/6324776')
|
||||||
|
('url', u'http://talesfromtheoutback.blogspot.com/')
|
||||||
|
('url', u'http://www.webforefront.com/archives/2007/07/html_5.html#more')
|
||||||
|
('url', u'http://money.cnn.com/2007/07/23/technology/bc.opsware.hp.reut/index.htm?postversion=2007072308')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=-x7JYADhWIo')
|
||||||
|
('url', u'http://sco.wikipedia.org/wiki/Main_Page')
|
||||||
|
('url', u'http://woozle.org/~brian/NealeProxy64.gif')
|
||||||
|
('url', u'http://www.mapmsg.com/games/statetris/usa/')
|
||||||
|
('url', u'http://online.wsj.com/article/SB118522167698875274.html?mod=googlenews_wsj')
|
||||||
|
('url', u'http://www.makezine.com/blog/archive/2006/08/how_to_make_a_time_fountain.html')
|
||||||
|
('url', u'http://www.amazon.com/Super-Crunchers-Thinking-Numbers-Smart/dp/0553805401/ref=pd_bbs_1/102-4370334-6727360?ie=UTF8&s=books&qid=1185387904&sr=8-1')
|
||||||
|
('url', u'http://www.amazon.com/gp/product/0061243582/ref=reg_hu-wl_item-added/102-4370334-6727360')
|
||||||
|
('url', u'http://www.joelonsoftware.com/items/2007/07/20.html')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=lWWKBY7gx_0')
|
||||||
|
('url', u'http://www.politics.ie/viewtopic.php?t=24815')
|
||||||
|
('url', u'http://www.time.com/time/business/article/0,8599,1644040,00.html')
|
||||||
|
('url', u'http://www.time.com/time/magazine/article/0,9171,1645145,00.html')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=lmDTSQtK20c')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=Lgh4B-SeV44')
|
||||||
|
('url', u'http://www.relentlesstoil.com')
|
||||||
|
('url', u'http://toastfloats.blogspot.com')
|
||||||
|
('url', u'http://www.vrbo.com/76037')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=9mSKBgvHdoE')
|
||||||
|
('url', u'http://www.cdc.gov/ncidod/dbmd/diseaseinfo/sporotrichosis_g.htm')
|
||||||
|
('url', u'http://www.pbs.org/cringely/pulpit/2007/pulpit_20070727_002573.html')
|
||||||
|
[]
|
||||||
|
('url', u'http://www.syncharger.com/kwiksynch.htm')
|
||||||
|
('url', u'http://addxorrol.blogspot.com/2007/07/ive-been-denied-entry-to-us-essentially.html')
|
||||||
|
('url', u'http://www.ducati.ms/forums/showthread.php?t=26856')
|
||||||
|
('url', u'http://www.credmp.org/index.php/2007/07/28/getting-things-done-in-emacs/')
|
||||||
|
('url', u'http://www.cnn.com/2007/SHOWBIZ/TV/07/30/people.mckellar.ap/('url', u'http://wdfw.wa.gov/fish/regs/2007/2007sportregs.pdf')
|
||||||
|
('url', u'http://www.factmonster.com/ipka/A0770817.html')
|
||||||
|
('url', u'http://www.lovopoly.com/index.php?gad=CMKngKgDEggigJqysq-YvhiQ0r39AyDeqoEV')
|
||||||
|
('url', u'http://www.cnn.com/2007/US/08/01/bridge.collapse.ap/index.html')
|
||||||
|
('url', u'http://tinyurl.com/9v426')
|
||||||
|
('url', u'http://thepiratebay.org/')
|
||||||
|
('url', u'http://www.slate.com/id/2171520')
|
||||||
|
('url', u'http://gofugyourself.typepad.com/go_fug_yourself/2007/08/carrot-fug.html')
|
||||||
|
('url', u'http://img.photobucket.com/albums/v719/gofugyourself/GFY112005/72792964.jpg')
|
||||||
|
('url', u'http://img.photobucket.com/albums/v719/gofugyourself/GFY112005/56397985.jpg')
|
||||||
|
('url', u'http://img.photobucket.com/albums/v352/morganzola/gfy/74391262.jpg')
|
||||||
|
('url', u'http://img.photobucket.com/albums/v719/gofugyourself/GFY112005/73158808.jpg')
|
||||||
|
('url', u'http://blog.guykawasaki.com/2007/06/no-plan-no-capi.html')
|
||||||
|
('url', u'http://whoishotter.com/')
|
||||||
|
('url', u'http://www.apologeticsindex.org/s51aa.html')
|
||||||
|
('url', u'http://tinyurl.com/232cwj')
|
||||||
|
('url', u'http://tinyurl.com/2hlrgj')
|
||||||
|
('url', u'http://www.amazon.com/gp/product/B00004XOM3')
|
||||||
|
('url', u'http://www.flickr.com/photos/kirkpetersen/994648322/')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=II_-QcW4Q4I')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=ylAHWVuPNus&mode=related&search=')
|
||||||
|
('url', u'http://erikstambaugh.com/img/boobies-censored.jpg')
|
||||||
|
('url', u'http://mikecane.wordpress.com/2007/07/10/iphone-death-star-upgrade-coming/')
|
||||||
|
('url', u'http://blog.wired.com/27bstroke6/2007/08/media-mole-at-d.html')
|
||||||
|
('url', u'http://www.lagrange.org/articles/Y05M05/a-lacticacid-rf.htm')
|
||||||
|
('url', u'http://www.lagrange.org/articles/Y05M05/a-lacticacid-rf.htm')
|
||||||
|
('url', u'http://www.thelocalvine.com/')
|
||||||
|
('url', u'http://www.infoq.com/news/2007/08/gemstone-ruby')
|
||||||
|
('url', u'http://youtube.com/watch?v=hEtZHOj4vHo')
|
||||||
|
('url', u'http://www.amazon.com/gp/product/0486244261/ref=wl_it_dp/102-4370334-6727360?ie=UTF8&coliid=I17UBOFOS6PCOC&colid=5FKZUGCNGYFN')
|
||||||
|
('url', u'http://www.nytimes.com/2007/07/26/arts/television/26stew.html?ex=1186545600&en=b6e621bc81e010bb&ei=5070')
|
||||||
|
('url', u'http://www.amazon.com/Orion-SkyQuest-Telescope-Accessory-Crayford-Style/dp/B000M86EOQ')
|
||||||
|
('url', u'http://www.amazon.com/Orion-T-Ring-Canon-including-Digital/dp/B0000XMUKK')
|
||||||
|
('url', u'http://httpd.apache.org/docs/2.0/mod/mod_headers.html')
|
||||||
|
('url', u'http://highscalability.com/digg-architecture')
|
||||||
|
('url', u'http://www.amazon.com/Inner-Loops-Sourcebook-Software-Development/dp/0201479605')
|
||||||
|
('url', u'http://www.microsoft.com/mac/products/entourage2004/entourage2004.aspx?pid=howtobuy')
|
||||||
|
('url', u'http://www.amazon.com/')
|
||||||
|
('url', u'http://validator.w3.org/check?uri=www.amazon.com')
|
||||||
|
('url', u'http://www.woozle.org/')
|
||||||
|
('url', u'http://tinyurl.com/2gnmlz')
|
||||||
|
('url', u'http://tinyurl.com/2gnmlz')
|
||||||
|
('url', u'http://www.flickr.com/photos/hbunny/1063515089/')
|
||||||
|
('url', u'http://www.betterphoto.com/gallery/big.asp?photoID=4338308&catID=&style=&rowNumber=8&memberID=232272')
|
||||||
|
('url', u'http://jezebel.com/gossip/iphone-widow/the-iphone-is-cool-and-all-but-can-you-stick-your-dick-in-it-288400.php')
|
||||||
|
('url', u'http://store.apple.com/1-800-MY-APPLE/WebObjects/AppleStore.woa/wa/RSLID?mco=3C893A3B&nclm=CertifiedMac')
|
||||||
|
('url', u'http://store.apple.com/1-800-MY-APPLE/WebObjects/AppleStore.woa/wa/RSLID?nnmm=browse&mco=4124CE5A&node=home/macbook/macbook')
|
||||||
|
('url', u'http://www.intertron.org')
|
||||||
|
('url', u'http://www.43folders.com/2006/11/10/smart-playlists-for-packrats/#besure')
|
||||||
|
('url', u'http://en.wikipedia.org/wiki/Floccinaucinihilipilification')
|
||||||
|
('url', u'http://en.wikipedia.org/wiki/Coupling_')
|
||||||
|
('url', u'http://www.flickr.com/photos/brainswax/')
|
||||||
|
('url', u'http://brain.lis.uiuc.edu:2323/opencms/export/sites/default/dhq/vol/001/2/000009.html')
|
||||||
|
('url', u'http://www.ratelasvegas.com/buffets/buffetrank.html')
|
||||||
|
('url', u'http://www.deceptionisland.aq/')
|
||||||
|
('url', u'http://www.flickr.com/photos/brainswax/1145435885/')
|
||||||
|
('url', u'http://www.flickr.com/photos/brainswax/1145435885/')
|
||||||
|
('url', u'http://www.crocodile-trophy.com/info_forms_2007/CT-INFO%20ENG%202007-k.pdf')
|
||||||
|
('url', u'http://www.crocodile-trophy.com/crocodiletrophy/en/index_en.htm')
|
||||||
|
('url', u'http://www.adv-cycling.org/transam/index.cfm')
|
||||||
|
('url', u'http://www.adv-cycling.org/transam/index.cfm')
|
||||||
|
('url', u'http://www.adv-cycling.org/routes/greatdivide.cfm')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=pY8jaGs7xJ0')
|
||||||
|
('url', u'http://www.filmschoolrejects.com/news/great-new-hi-res-images-from-the-dark-knight.php')
|
||||||
|
('url', u'http://docs.info.apple.com/article.html?artnum=25668')
|
||||||
|
('url', u'https://woozle.org/blogs/neale/2007-08-17T23:06:36Z')
|
||||||
|
('url', u'http://www.moo.com/products/minicards.php')
|
||||||
|
('url', u'http://www.brunching.com/images/geekchartbig.gif')
|
||||||
|
('url', u'http://www.flickr.com/photos/brainswax/')
|
||||||
|
('url', u'http://www.snopes.com/business/genius/spacepen.asp')
|
||||||
|
('url', u'http://www.bhphotovideo.com/c/product/12066-USA/Canon_2508A002_Super_Wide_Angle_EF.html')
|
||||||
|
('url', u'http://woozle.org/albums/neale/Misc/Art/Miscellaneous/flors.jpg?html=1')
|
||||||
|
('url', u'http://woozle.org/albums/neale/Misc/Art/Miscellaneous/real_body_hart.jpg?html=1')
|
||||||
|
('url', u'http://woozle.org/albums/neale/Misc/Art/Miscellaneous/firebox.jpg?html=1')
|
||||||
|
('url', u'http://www.glumbert.com/media/tonguetwister')
|
||||||
|
('url', u'http://216.254.28.103/schmancy.jpg')
|
||||||
|
('url', u'http://www.flickr.com/photos/hbunny/1156562400/')
|
||||||
|
('url', u'http://www.youtube.com/watch?v=bgRnVhbfIKQ')
|
||||||
|
('url', u'http://www.engadget.com/2007/08/21/toshibas-320gb-2-5-inch-hard-drive-a-worlds-best-for-laptops/')
|
||||||
|
('url', u'http://seattletimes.nwsource.com/html/localnews/2003847538_ferries22m.html')
|
||||||
|
('url', u'http://www.microsoft.com/silverlight/default_ns.aspx')
|
||||||
|
('url', u'http://seattlepi.nwsource.com/local/328555_apple22.html')
|
||||||
|
('url', u'http://woozle.org/irc/chat/')
|
||||||
|
('url', u'http://woozle.org/~brian/jaime.jpg')
|
||||||
|
('url', u'http://woozle.org/~brian/jaime-exposed.jpg')
|
||||||
|
('url', u'http://www.flickr.com/photo_zoom.gne?id=1219342723&size=o')
|
||||||
|
('url', u'http://www.onlineshoes.com/synopsispage.asp?type=brand&brandid=9&brandcatid=1792&ageid=1&gen=m')
|
||||||
|
('url', u'http://forums.commercialsihate.com/forum_posts.asp?TID=7337')
|
||||||
|
('url', u'http://www.marmot
|
|
@ -0,0 +1 @@
|
||||||
|
1552
|
|
@ -0,0 +1,114 @@
|
||||||
|
#! /usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import firebot
|
||||||
|
import irc
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
from procbot import ProcBot, Runner
|
||||||
|
|
||||||
|
def esc(arg):
|
||||||
|
return "'" + arg.replace("'", r"'\''") + "'"
|
||||||
|
|
||||||
|
def lesc(args):
|
||||||
|
return [esc(arg) for arg in args]
|
||||||
|
|
||||||
|
class Gallium(firebot.FireBot, ProcBot):
|
||||||
|
opall = False
|
||||||
|
bindings = []
|
||||||
|
|
||||||
|
def cmd_invite(self, sender, forum, addl):
|
||||||
|
# Join any channel to which we're invited
|
||||||
|
self.write('JOIN', forum.name())
|
||||||
|
|
||||||
|
def cmd_join(self, sender, forum, addl):
|
||||||
|
#firebot.FireBot.cmd_join(self, sender, forum, addl)
|
||||||
|
if self.opall:
|
||||||
|
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.')
|
||||||
|
else:
|
||||||
|
# Otherwise, op the user
|
||||||
|
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 server_status(self, sender, forum, addl, match):
|
||||||
|
loadavg = file('/proc/loadavg').read().strip()
|
||||||
|
io_status = file('/proc/io_status').read().strip()
|
||||||
|
forum.msg('%s; load %s' % (io_status, loadavg))
|
||||||
|
bindings.append((re.compile(r"^\008[:, ]+server status"),
|
||||||
|
server_status))
|
||||||
|
|
||||||
|
def unsafe_eval(self, sender, forum, addl, match):
|
||||||
|
if self.debug:
|
||||||
|
txt = match.group(1)
|
||||||
|
r = eval(txt)
|
||||||
|
forum.msg('%s: %r' % (sender.name(), r))
|
||||||
|
bindings.append((re.compile(r"^\008[:, ]+eval (.*)$"),
|
||||||
|
unsafe_eval))
|
||||||
|
|
||||||
|
def randglyph(self, sender, forum, addl, match):
|
||||||
|
count = 0
|
||||||
|
tries = []
|
||||||
|
while count < 6:
|
||||||
|
i = random.randint(0, 0xffff)
|
||||||
|
k = 'U+%04x' % i
|
||||||
|
tries.append(k)
|
||||||
|
r = self.get(k)
|
||||||
|
if r:
|
||||||
|
forum.msg('%s %s' % (k, r))
|
||||||
|
return
|
||||||
|
count += 1
|
||||||
|
forum.msg("Nothing found (tried %s)" % tries)
|
||||||
|
bindings.append((re.compile(r"^u\+rand$"),
|
||||||
|
randglyph))
|
||||||
|
|
||||||
|
def runcmd(self, sender, forum, addl, match):
|
||||||
|
command = match.group('command')
|
||||||
|
args = match.group('args').split(' ')
|
||||||
|
args = [x.replace("'", "'\\''") for x in args]
|
||||||
|
argstr = ' '.join(args)
|
||||||
|
Runner('%s %s' % (command, argstr),
|
||||||
|
lambda l,r: self.proc_cb('%s: ' % command, sender, forum, l, r))
|
||||||
|
bindings.append((re.compile(r"^(?P<command>whois) +(?P<args>.*)$"),
|
||||||
|
runcmd))
|
||||||
|
bindings.append((re.compile(r"^(?P<command>host) +(?P<args>.*)$"),
|
||||||
|
runcmd))
|
||||||
|
|
||||||
|
bindings.extend(firebot.FireBot.bindings)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import shorturl
|
||||||
|
import socket
|
||||||
|
import daemon
|
||||||
|
import sys
|
||||||
|
|
||||||
|
debug = False
|
||||||
|
if "-d" in sys.argv:
|
||||||
|
debug = True
|
||||||
|
|
||||||
|
if not debug:
|
||||||
|
# Become a daemon
|
||||||
|
log = file('gallium.log', 'a')
|
||||||
|
daemon.daemon('gallium.pid', log, log)
|
||||||
|
|
||||||
|
# Short URL server
|
||||||
|
us = shorturl.start(('', 0))
|
||||||
|
firebot.URLSERVER = (socket.gethostbyaddr(socket.gethostname())[0],
|
||||||
|
us.getsockname()[1])
|
||||||
|
|
||||||
|
gallium = Gallium(('fozzie.woozle.org', 6667),
|
||||||
|
['gallium'],
|
||||||
|
"I'm a little printf, short and stdout",
|
||||||
|
["#woozle", "#gallium"])
|
||||||
|
gallium.debug = debug
|
||||||
|
|
||||||
|
irc.run_forever(0.5)
|
|
@ -0,0 +1,7 @@
|
||||||
|
#! /bin/sh
|
||||||
|
## Restart the bot if it's not running
|
||||||
|
|
||||||
|
# Gallium assumes everything's in the cwd
|
||||||
|
cd /home/neale/src/firebot
|
||||||
|
|
||||||
|
kill -0 `cat gallium.pid` 2>/dev/null || ./gallium.py
|
|
@ -0,0 +1,301 @@
|
||||||
|
from bindingsbot import BindingsBot
|
||||||
|
import re
|
||||||
|
import irc
|
||||||
|
import seedyb
|
||||||
|
import time
|
||||||
|
|
||||||
|
class InfoBot(BindingsBot):
|
||||||
|
"""A cheap knock-off of the famous InfoBot.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
msg_cat = {}
|
||||||
|
msg_cat.update(BindingsBot.msg_cat)
|
||||||
|
bindings = []
|
||||||
|
|
||||||
|
def __init__(self, host, nicks, gecos, channels, dbname='info.cdb'):
|
||||||
|
BindingsBot.__init__(self, host, nicks, gecos, channels)
|
||||||
|
self._db = seedyb.open(dbname)
|
||||||
|
self.seen = {}
|
||||||
|
self.ignore_case = True
|
||||||
|
|
||||||
|
def sync(self):
|
||||||
|
now = time.time()
|
||||||
|
self._db.sync()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.sync()
|
||||||
|
BindingsBot.close(self)
|
||||||
|
|
||||||
|
def cmd_ping(self, sender, forum, addl):
|
||||||
|
BindingsBot.cmd_ping(self, sender, forum, addl)
|
||||||
|
self.sync()
|
||||||
|
|
||||||
|
|
||||||
|
msg_cat['unknown'] = ("I don't know anything about %(key)s, %(sender)s.",)
|
||||||
|
msg_cat['stats'] = ("I know about %(things)s things.",)
|
||||||
|
msg_cat['is'] = ("Rumor has it that %(key)s is %(val)s",
|
||||||
|
"I believe %(key)s is %(val)s",
|
||||||
|
"My sources tell me %(key)s is %(val)s",
|
||||||
|
'Gosh, %(sender)s, I think %(key)s is %(val)s',
|
||||||
|
"%(key)s is %(val)s")
|
||||||
|
msg_cat['_is_'] = ("%(key)s is %(val)s",)
|
||||||
|
msg_cat['dunno'] = ("Search me, %(sender)s.",
|
||||||
|
"I have no earthly idea, %(sender)s.",
|
||||||
|
"I wish I knew, %(sender)s.")
|
||||||
|
msg_cat['same'] = ('I already had it that way, %(sender)s.',
|
||||||
|
"That's what I have for %(key)s too, %(sender)s.")
|
||||||
|
msg_cat['but'] = ('...but %(key)s is %(old)s',)
|
||||||
|
msg_cat['locked'] = ('Sorry, %(sender)s, %(key)s is locked.',)
|
||||||
|
msg_cat['tell'] = ('%(sender)s wants you to know: %(string)s',)
|
||||||
|
msg_cat['synced'] = ('Synchronized in %(time)f seconds.',)
|
||||||
|
|
||||||
|
|
||||||
|
def do_sync(self, sender, forum, addl, match):
|
||||||
|
now = time.time()
|
||||||
|
self.sync()
|
||||||
|
forum.msg(self.gettext('synced',
|
||||||
|
sender=sender.name(),
|
||||||
|
time=(time.time() - now)))
|
||||||
|
bindings.append((re.compile(r"^\008[,: ]+(sync|synchronize|flush)$"),
|
||||||
|
do_sync))
|
||||||
|
|
||||||
|
def encode_key(self, key):
|
||||||
|
if self.ignore_case:
|
||||||
|
key = key.lower()
|
||||||
|
return key
|
||||||
|
|
||||||
|
def get(self, key, *args, **kwargs):
|
||||||
|
return self._db.get(self.encode_key(key), *args, **kwargs)
|
||||||
|
|
||||||
|
def getall(self, key, **kwargs):
|
||||||
|
return self._db.getall(self.encode_key(key), **kwargs)
|
||||||
|
|
||||||
|
def set(self, key, val, **kwargs):
|
||||||
|
return self._db.set(self.encode_key(key), val, **kwargs)
|
||||||
|
|
||||||
|
def delete(self, key, **kwargs):
|
||||||
|
self._db.delete(self.encode_key(key), **kwargs)
|
||||||
|
|
||||||
|
def lock(self, key):
|
||||||
|
return self._db.lock(self.encode_key(key))
|
||||||
|
|
||||||
|
def unlock(self, key):
|
||||||
|
return self._db.unlock(self.encode_key(key))
|
||||||
|
|
||||||
|
def stats(self, sender, forum, addl, match):
|
||||||
|
forum.msg(self.gettext('stats', things=len(self._db)))
|
||||||
|
bindings.append((re.compile(r"^\008[,: ]+statu?s$"),
|
||||||
|
stats))
|
||||||
|
|
||||||
|
|
||||||
|
# Delete part of an entry
|
||||||
|
def forget_from(self, sender, forum, key, substr):
|
||||||
|
val = self.getall(key)
|
||||||
|
if not val:
|
||||||
|
raise KeyError()
|
||||||
|
|
||||||
|
possibilities = []
|
||||||
|
newval = []
|
||||||
|
for i in val:
|
||||||
|
if substr in i:
|
||||||
|
possibilities.append(i)
|
||||||
|
else:
|
||||||
|
newval.append(i)
|
||||||
|
|
||||||
|
if len(possibilities) == 1:
|
||||||
|
try:
|
||||||
|
self.set(key, tuple(newval))
|
||||||
|
except seedyb.Locked:
|
||||||
|
forum.msg(self.gettext('locked', key=key,
|
||||||
|
sender=sender.name()))
|
||||||
|
return
|
||||||
|
forum.msg(self.gettext('forgot',
|
||||||
|
key=key,
|
||||||
|
val=possibilities[0],
|
||||||
|
sender=sender.name()))
|
||||||
|
elif len(possibilities) == 0:
|
||||||
|
forum.msg(self.gettext('not in',
|
||||||
|
key=key,
|
||||||
|
substr=substr,
|
||||||
|
sender=sender.name()))
|
||||||
|
else:
|
||||||
|
forum.msg(self.gettext('ambiguous forget',
|
||||||
|
key=key,
|
||||||
|
substr=substr,
|
||||||
|
num=len(possibilities),
|
||||||
|
sender=sender.name()))
|
||||||
|
msg_cat['not in'] = ("I don't see any entries for %(key)s containing %(substr)s, %(sender)s",)
|
||||||
|
msg_cat['ambiguous forget'] = ("There are %(num)d matches for %(substr)s in %(key)s. Try a more specific substring!",)
|
||||||
|
msg_cat['forgot'] = ('Okay, %(sender)s, I forgot \"%(val)s\" from \"%(key)s\".',)
|
||||||
|
|
||||||
|
# Delete an entry
|
||||||
|
def forget(self, sender, forum, addl, match):
|
||||||
|
key = match.group('key')
|
||||||
|
ekey = self.encode_key(key)
|
||||||
|
try:
|
||||||
|
self.delete(ekey)
|
||||||
|
forum.msg(self.gettext('okay', key=key, sender=sender.name()))
|
||||||
|
except KeyError:
|
||||||
|
if ' from ' in key:
|
||||||
|
substr, k = key.split(' from ', 1)
|
||||||
|
try:
|
||||||
|
return self.forget_from(sender, forum, k, substr)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
forum.msg(self.gettext('unknown', key=key, sender=sender.name()))
|
||||||
|
except seedyb.Locked:
|
||||||
|
forum.msg(self.gettext('locked', key=key,
|
||||||
|
sender=sender.name()))
|
||||||
|
bindings.append((re.compile(r"^\008[,: ]+forget (?P<key>.+)$", re.IGNORECASE),
|
||||||
|
forget))
|
||||||
|
|
||||||
|
# Lock an entry
|
||||||
|
def lock_entry(self, sender, forum, addl, match):
|
||||||
|
key = match.group('key')
|
||||||
|
self.lock(key)
|
||||||
|
forum.msg(self.gettext('okay', key=key, sender=sender.name()))
|
||||||
|
bindings.append((re.compile(r"^\008[,: ]+lock (?P<key>.+)$", re.IGNORECASE),
|
||||||
|
lock_entry))
|
||||||
|
|
||||||
|
# Unlock an entry
|
||||||
|
def unlock_entry(self, sender, forum, addl, match):
|
||||||
|
key = match.group('key')
|
||||||
|
self.unlock(key)
|
||||||
|
forum.msg(self.gettext('okay', key=key, sender=sender.name()))
|
||||||
|
bindings.append((re.compile(r"^\008[,: ]+unlock (?P<key>.+)$", re.IGNORECASE),
|
||||||
|
unlock_entry))
|
||||||
|
|
||||||
|
# Literal entry
|
||||||
|
def literal(self, sender, forum, addl, match):
|
||||||
|
key = match.group('key')
|
||||||
|
val = self.getall(key)
|
||||||
|
if val:
|
||||||
|
sv = `val`
|
||||||
|
out = []
|
||||||
|
while len(sv) > 300:
|
||||||
|
s = sv[:300]
|
||||||
|
sv = sv[300:]
|
||||||
|
out.append('db[%r] == %s ...' % (key, s))
|
||||||
|
out.append('db[%r] == %s' % (key, sv))
|
||||||
|
self.despool(forum, out)
|
||||||
|
else:
|
||||||
|
forum.msg(self.gettext('unknown', key=key, sender=sender.name()))
|
||||||
|
bindings.append((re.compile(r"^\008[,: ]+literal (?P<key>.+)$", re.IGNORECASE),
|
||||||
|
literal))
|
||||||
|
|
||||||
|
# Look something up in the DB
|
||||||
|
def lookup(self, sender, forum, addl, match):
|
||||||
|
key = match.group('key')
|
||||||
|
|
||||||
|
# Try looking it up verbatim
|
||||||
|
val = self.get(key)
|
||||||
|
if not val:
|
||||||
|
# Try the cleaned version
|
||||||
|
key = key.rstrip('.?! ')
|
||||||
|
val = self.get(key)
|
||||||
|
if val:
|
||||||
|
val = val % {'me': self.nick,
|
||||||
|
'forum': forum.name(),
|
||||||
|
'sender': sender.name()}
|
||||||
|
if len(val) > 300:
|
||||||
|
val = val[:297] + '...'
|
||||||
|
if val[0] == '\\':
|
||||||
|
forum.msg(val[1:])
|
||||||
|
elif val[0] == ':':
|
||||||
|
forum.act(val[1:])
|
||||||
|
else:
|
||||||
|
forum.msg(self.gettext('is', key=key, val=val, sender=sender.name()))
|
||||||
|
elif match.group('me'):
|
||||||
|
forum.msg(self.gettext('dunno', key=key, sender=sender.name()))
|
||||||
|
elif match.group('question'):
|
||||||
|
# Don't allow storage of things like 'what is that?'
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def do_store(self, sender, forum, key, val, me, no, also):
|
||||||
|
resp = False
|
||||||
|
old = self.getall(key)
|
||||||
|
okay = self.gettext('okay', sender=sender.name())
|
||||||
|
if old:
|
||||||
|
if val in old:
|
||||||
|
if me:
|
||||||
|
resp = self.gettext('same', key=key, val=val, old=old,
|
||||||
|
sender=sender.name())
|
||||||
|
else:
|
||||||
|
# Ignore duplicates
|
||||||
|
resp = self.gettext('same', key=key, val=val, old=old,
|
||||||
|
sender=sender.name())
|
||||||
|
pass
|
||||||
|
elif me:
|
||||||
|
if also:
|
||||||
|
self.set(key, old + [val])
|
||||||
|
resp = okay
|
||||||
|
elif no:
|
||||||
|
self.set(key, [val])
|
||||||
|
resp = okay
|
||||||
|
else:
|
||||||
|
if len(old) == 1:
|
||||||
|
old = old[0]
|
||||||
|
resp = self.gettext('but', key=key, val=val, old=old,
|
||||||
|
sender=sender.name())
|
||||||
|
else:
|
||||||
|
self.set(key, old + [val])
|
||||||
|
resp = okay
|
||||||
|
else:
|
||||||
|
self.set(key, (val,))
|
||||||
|
resp = okay
|
||||||
|
|
||||||
|
if resp:
|
||||||
|
if me:
|
||||||
|
forum.msg(resp)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Write a new value to the DB
|
||||||
|
def store(self, sender, forum, addl, match):
|
||||||
|
key = match.group('key')
|
||||||
|
val = match.group('val')
|
||||||
|
# Change % to %%, except for %(
|
||||||
|
val = val.replace('%', '%%')
|
||||||
|
val = val.replace('%(', '(')
|
||||||
|
me = match.group('me')
|
||||||
|
no = match.group('no') and me
|
||||||
|
also = val.startswith('also ')
|
||||||
|
if also:
|
||||||
|
val = val[5:]
|
||||||
|
return self.do_store(sender, forum, key, val, me, no, also)
|
||||||
|
|
||||||
|
def append_cmd(self, sender, forum, addl, match):
|
||||||
|
key = match.group('key')
|
||||||
|
val = match.group('val')
|
||||||
|
return self.do_store(sender, forum, key, val, me=True, no=True, also=True)
|
||||||
|
|
||||||
|
# Pull in BindingsBot things
|
||||||
|
bindings.extend(BindingsBot.bindings)
|
||||||
|
|
||||||
|
# This is first to prevent storing "firebot: what is foo?"
|
||||||
|
bindings.append((re.compile(r"^(?P<me>\008[,: ]+)?(?P<question>(what|who|where|wtf).*('s|'re| is| are) )(?P<key>.+)$",
|
||||||
|
re.IGNORECASE),
|
||||||
|
lookup))
|
||||||
|
bindings.append((re.compile(r"^(?P<me>\008[,: ]+)append (?P<key>.+) <= (?P<val>.+)",
|
||||||
|
re.IGNORECASE),
|
||||||
|
append_cmd))
|
||||||
|
bindings.append((re.compile((r"^(?P<me>\008)[,: ]+(?P<no>no, *)"
|
||||||
|
r"(?P<key>.+?) (is|are) (?P<val>.+)$"),
|
||||||
|
re.IGNORECASE),
|
||||||
|
store))
|
||||||
|
bindings.append((re.compile((r"^(?P<no>no,? *)?(?P<me>\008)[,: ]+"
|
||||||
|
r"(?P<key>.+?) (is|are) (?P<val>.+)$"),
|
||||||
|
re.IGNORECASE),
|
||||||
|
store))
|
||||||
|
bindings.append((re.compile(r"^([^:, ]+[:,] *)?(?P<no>)(?P<me>)(?P<key>.+) (is|are) (?P<val>.+)$",
|
||||||
|
re.IGNORECASE),
|
||||||
|
store))
|
||||||
|
bindings.append((re.compile(r"^(?P<me>\008[,: ]+)?(?P<question>)(?P<key>.+)$",
|
||||||
|
re.IGNORECASE),
|
||||||
|
lookup))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,604 @@
|
||||||
|
import asynchat
|
||||||
|
import asyncore
|
||||||
|
import socket
|
||||||
|
import string
|
||||||
|
import types
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
import time
|
||||||
|
|
||||||
|
class IRCHandler(asynchat.async_chat):
|
||||||
|
"""IRC Server connection.
|
||||||
|
|
||||||
|
This is the one you want to derive your connection classes from.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
debug = False
|
||||||
|
heartbeat_interval = 1 # seconds per heartbeat
|
||||||
|
|
||||||
|
def __init__(self, host=None, nick=None, gecos=None):
|
||||||
|
asynchat.async_chat.__init__(self)
|
||||||
|
self.line = ''
|
||||||
|
self.timers = []
|
||||||
|
self.last_heartbeat = 0
|
||||||
|
self.set_terminator('\r\n')
|
||||||
|
if host:
|
||||||
|
self.open_connection(host, nick, gecos)
|
||||||
|
|
||||||
|
def dbg(self, msg):
|
||||||
|
if self.debug:
|
||||||
|
print msg
|
||||||
|
|
||||||
|
def open_connection(self, host, nick, gecos):
|
||||||
|
self.nick = nick
|
||||||
|
self.gecos = gecos
|
||||||
|
self.host = host
|
||||||
|
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self.connect(host)
|
||||||
|
|
||||||
|
def handle_connect(self):
|
||||||
|
self.write(['NICK', self.nick])
|
||||||
|
self.write(['USER', self.nick, '+iw', self.nick], self.gecos)
|
||||||
|
|
||||||
|
def connect(self, host):
|
||||||
|
self.waiting = False
|
||||||
|
asynchat.async_chat.connect(self, host)
|
||||||
|
|
||||||
|
def heartbeat(self):
|
||||||
|
"""Invoke all timers."""
|
||||||
|
|
||||||
|
if not self.timers:
|
||||||
|
return
|
||||||
|
timers, self.timers = self.timers, []
|
||||||
|
now = time.time()
|
||||||
|
for t, cb in timers:
|
||||||
|
if t > now:
|
||||||
|
self.timers.append((t, cb))
|
||||||
|
else:
|
||||||
|
cb()
|
||||||
|
|
||||||
|
def add_timer(self, secs, callback):
|
||||||
|
"""After secs seconds, call callback"""
|
||||||
|
self.timers.append((time.time() + secs, callback))
|
||||||
|
|
||||||
|
def readable(self):
|
||||||
|
"""Called by asynchat to see if we're readable.
|
||||||
|
|
||||||
|
We hook our heartbeat in here.
|
||||||
|
"""
|
||||||
|
|
||||||
|
now = time.time()
|
||||||
|
if now > self.last_heartbeat + self.heartbeat_interval:
|
||||||
|
self.heartbeat()
|
||||||
|
self.last_heartbeat = now
|
||||||
|
|
||||||
|
if self.connected:
|
||||||
|
return asynchat.async_chat.readable(self)
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def collect_incoming_data(self, data):
|
||||||
|
"""Called by asynchat when data arrives"""
|
||||||
|
self.line += data
|
||||||
|
|
||||||
|
def found_terminator(self):
|
||||||
|
"""Called by asynchat when it finds the terminating character.
|
||||||
|
"""
|
||||||
|
line = self.line
|
||||||
|
self.line = ''
|
||||||
|
self.parse_line(line)
|
||||||
|
|
||||||
|
def write(self, args, *text):
|
||||||
|
"""Send out an IRC command
|
||||||
|
|
||||||
|
This function helps to prevent you from shooting yourself in the
|
||||||
|
foot, by forcing you to send commands that are in a valid format
|
||||||
|
(although it doesn't check the validity of the actual commands).
|
||||||
|
|
||||||
|
As we all know, IRC commands take the form
|
||||||
|
|
||||||
|
:COMMAND ARG1 ARG2 ARG3 ... :text string
|
||||||
|
|
||||||
|
where 'text string' is optional. Well, that's exactly how this
|
||||||
|
function works. Args is a list of length at least one, and text
|
||||||
|
string can be any number of strings. You can call the function
|
||||||
|
like this:
|
||||||
|
|
||||||
|
write(['PRIVMSG', nick], 'Hello 12')
|
||||||
|
|
||||||
|
or like this:
|
||||||
|
|
||||||
|
write(['PRIVMSG', nick], 'Hello', '12')
|
||||||
|
|
||||||
|
or even like this:
|
||||||
|
|
||||||
|
write(['PRIVMSG', nick], 'Hello', 12)
|
||||||
|
|
||||||
|
And you'll get the same result with all three.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if (type(args) == types.StringType):
|
||||||
|
cmd = args
|
||||||
|
else:
|
||||||
|
cmd = u' '.join(args)
|
||||||
|
cmdstr = cmd
|
||||||
|
if (text):
|
||||||
|
txt = ''
|
||||||
|
for t in (text):
|
||||||
|
if type(t) in (types.StringType, types.UnicodeType):
|
||||||
|
txt = txt + ' ' + t
|
||||||
|
elif type(t) in (types.ListType, types.TupleType):
|
||||||
|
for i in (t):
|
||||||
|
try:
|
||||||
|
txt = ' '.join([txt, i])
|
||||||
|
except TypeError:
|
||||||
|
txt = ' '.join([txt, repr(i)])
|
||||||
|
else:
|
||||||
|
txt = ' '.join([txt, repr(t)])
|
||||||
|
txt = txt[1:]
|
||||||
|
cmdstr = "%s :%s" % (cmdstr, txt)
|
||||||
|
encstr = cmdstr.encode('utf8', 'replace')
|
||||||
|
self.dbg("-> %s " % encstr)
|
||||||
|
try:
|
||||||
|
self.send(encstr + '\n')
|
||||||
|
except socket.error:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def parse_line(self, line):
|
||||||
|
"""Parse a server-provided line
|
||||||
|
|
||||||
|
This does all the magic of parsing those ill-formatted IRC
|
||||||
|
messages. It will also decide if a PRIVMSG or NOTICE is using
|
||||||
|
CTCP (the client-to-client protocol, which by convention is any
|
||||||
|
of the above messages with ^A on both ends of the text.
|
||||||
|
|
||||||
|
This function goes on to invoke self.eval_triggers on the parsed
|
||||||
|
data like this:
|
||||||
|
|
||||||
|
self.eval_triggers(operation, arguments, text)
|
||||||
|
|
||||||
|
where operation and text are strings, and arguments is a list.
|
||||||
|
|
||||||
|
It returns the same tuple (op, args, text).
|
||||||
|
|
||||||
|
"""
|
||||||
|
line = line.decode('utf8', 'replace')
|
||||||
|
if (line[0] == ':'):
|
||||||
|
with_uname = 1
|
||||||
|
line = line [1:]
|
||||||
|
else:
|
||||||
|
with_uname = 0
|
||||||
|
try:
|
||||||
|
[args, text] = line.split(' :', 1)
|
||||||
|
args = args.split()
|
||||||
|
except ValueError:
|
||||||
|
args = line.split()
|
||||||
|
text = ''
|
||||||
|
if (with_uname != 1):
|
||||||
|
op = args[0]
|
||||||
|
elif ((args[1] in ["PRIVMSG", "NOTICE"]) and
|
||||||
|
(text and (text[0] == '\001') and (text[-1] == '\001'))):
|
||||||
|
op = "C" + args[1]
|
||||||
|
text = text[1:-1]
|
||||||
|
else:
|
||||||
|
op = args[1]
|
||||||
|
self.dbg("<- %s %s %s" % (op, args, text))
|
||||||
|
self.handle(op, args, text)
|
||||||
|
return (op, args, text)
|
||||||
|
|
||||||
|
|
||||||
|
def handle(self, op, args, text):
|
||||||
|
"""Take action on a server message
|
||||||
|
|
||||||
|
Right now, just invokes
|
||||||
|
|
||||||
|
self.do_[op](args, text)
|
||||||
|
|
||||||
|
where [op] is the operation passed in.
|
||||||
|
|
||||||
|
This is a good method to overload if you want a really advanced
|
||||||
|
client supporting bindings.
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
method = getattr(self, "do_" + lower(op))
|
||||||
|
except AttributeError:
|
||||||
|
self.dbg("Unhandled: %s" % (op, args, text))
|
||||||
|
return
|
||||||
|
method(args, text)
|
||||||
|
|
||||||
|
|
||||||
|
class Recipient:
|
||||||
|
"""Abstract recipient object"""
|
||||||
|
|
||||||
|
def __init__(self, interface, name):
|
||||||
|
self._interface = interface
|
||||||
|
self._name = name
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'Recipient(%s)' % self.name()
|
||||||
|
|
||||||
|
def name(self):
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
def is_channel(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def write(self, cmd, addl):
|
||||||
|
"""Write a raw IRC command to our interface"""
|
||||||
|
|
||||||
|
self._interface.write(cmd, addl)
|
||||||
|
|
||||||
|
def cmd(self, cmd, text):
|
||||||
|
"""Send a command to ourself"""
|
||||||
|
|
||||||
|
self.write([cmd, self._name], text)
|
||||||
|
|
||||||
|
def msg(self, text):
|
||||||
|
"""Tell the recipient something"""
|
||||||
|
|
||||||
|
self.cmd("PRIVMSG", text)
|
||||||
|
|
||||||
|
def notice(self, text):
|
||||||
|
"""Send a notice to the recipient"""
|
||||||
|
|
||||||
|
self.cmd("NOTICE", text)
|
||||||
|
|
||||||
|
def ctcp(self, command, text):
|
||||||
|
"""Send a CTCP command to the recipient"""
|
||||||
|
|
||||||
|
return self.msg("\001%s %s\001" % (upper(command), text))
|
||||||
|
|
||||||
|
def act(self, text):
|
||||||
|
"""Send an action to the recipient"""
|
||||||
|
|
||||||
|
return self.ctcp("ACTION", text)
|
||||||
|
|
||||||
|
def cnotice(self, command, text):
|
||||||
|
"""Send a CTCP notice to the recipient"""
|
||||||
|
|
||||||
|
return self.notice("\001%s %s\001" % (upper(command), text))
|
||||||
|
|
||||||
|
class Channel(Recipient):
|
||||||
|
def __repr__(self):
|
||||||
|
return 'Channel(%s)' % self.name()
|
||||||
|
|
||||||
|
def is_channel(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
class User(Recipient):
|
||||||
|
def __init__(self, interface, name, user, host, op=False):
|
||||||
|
Recipient.__init__(self, interface, name)
|
||||||
|
self.user = user
|
||||||
|
self.host = host
|
||||||
|
self.op = op
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'User(%s, %s, %s)' % (self.name(), self.user, self.host)
|
||||||
|
|
||||||
|
def recipient(interface, name):
|
||||||
|
if name[0] in ["&", "#"]:
|
||||||
|
return Channel(interface, name)
|
||||||
|
else:
|
||||||
|
return User(interface, name, None, None)
|
||||||
|
|
||||||
|
class SmartIRCHandler(IRCHandler):
|
||||||
|
"""This is like the IRCHandler, except it creates Recipient objects
|
||||||
|
for IRC messages. The intent is to make it easier to write stuff
|
||||||
|
without knowledge of the IRC protocol.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def err(self, exception):
|
||||||
|
if self.debug:
|
||||||
|
traceback.print_exception(*exception)
|
||||||
|
|
||||||
|
def handle(self, op, args, text):
|
||||||
|
"""Parse more, creating objects and stuff
|
||||||
|
|
||||||
|
makes a call to self.handle_op(sender, forum, addl)
|
||||||
|
|
||||||
|
sender is always a Recipient object; if you want to reply
|
||||||
|
privately, you can send your reply to sender.
|
||||||
|
|
||||||
|
forum is a Recipient object corresponding with the forum over
|
||||||
|
which the message was carried. For user-to-user PRIVMSG and
|
||||||
|
NOTICE commands, this is the same as sender. For those same
|
||||||
|
commands sent to a channel, it is the channel. Thus, you can
|
||||||
|
always send a reply to forum, and it will be sent back in an
|
||||||
|
appropriate manner (ie. the way you expect).
|
||||||
|
|
||||||
|
addl is a tuple, containing additional information which might
|
||||||
|
be relelvant. Here's what it will contain, based on the server
|
||||||
|
operation:
|
||||||
|
|
||||||
|
op | addl
|
||||||
|
---------+----------------
|
||||||
|
PRIVMSG | text of the message
|
||||||
|
NOTICE | text of the notice
|
||||||
|
CPRIVMSG | CTCP command, text of the command
|
||||||
|
CNOTICE | CTCP response, text of the response
|
||||||
|
KICK * | victim of kick, kick text
|
||||||
|
MODE * | all mode args
|
||||||
|
JOIN * | empty
|
||||||
|
PART * | empty
|
||||||
|
QUIT | quit message
|
||||||
|
PING | ping text
|
||||||
|
NICK ! | old nickname
|
||||||
|
others | all arguments; text is last element
|
||||||
|
|
||||||
|
* The forum in these items is the channel to which the action
|
||||||
|
pertains.
|
||||||
|
! The sender for the NICK command is the *new* nickname. This
|
||||||
|
is so you can send messages to the sender object and they'll
|
||||||
|
go to the right place.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
sender = User(self, *unpack_nuhost(args))
|
||||||
|
except ValueError:
|
||||||
|
sender = None
|
||||||
|
forum = None
|
||||||
|
addl = ()
|
||||||
|
|
||||||
|
if op in ("PRIVMSG", "NOTICE"):
|
||||||
|
# PRIVMSG ['neale!~user@127.0.0.1', 'PRIVMSG', '#hydra'] firebot, foo
|
||||||
|
# PRIVMSG ['neale!~user@127.0.0.1', 'PRIVMSG', 'firebot'] firebot, foo
|
||||||
|
try:
|
||||||
|
if args[2][0] in '#&':
|
||||||
|
forum = recipient(self, args[2])
|
||||||
|
else:
|
||||||
|
forum = sender
|
||||||
|
addl = (text,)
|
||||||
|
except IndexError:
|
||||||
|
addl = (text, args[1])
|
||||||
|
elif op in ("CPRIVMSG", "CNOTICE"):
|
||||||
|
forum = recipient(self, args[2])
|
||||||
|
splits = text.split(" ")
|
||||||
|
if splits[0] == "DCC":
|
||||||
|
op = "DC" + op
|
||||||
|
addl = (splits[1],) + tuple(splits[2:])
|
||||||
|
else:
|
||||||
|
addl = (splits[0],) + tuple(splits[1:])
|
||||||
|
elif op in ("KICK",):
|
||||||
|
forum = recipient(self, args[2])
|
||||||
|
addl = (recipient(self, args[3]), text)
|
||||||
|
elif op in ("MODE",):
|
||||||
|
forum = recipient(self, args[2])
|
||||||
|
addl = args[3:]
|
||||||
|
elif op in ("JOIN", "PART"):
|
||||||
|
try:
|
||||||
|
forum = recipient(self, args[2])
|
||||||
|
except IndexError:
|
||||||
|
forum = recipient(self, text)
|
||||||
|
elif op in ("QUIT",):
|
||||||
|
addl = (text,)
|
||||||
|
elif op in ("PING", "PONG"):
|
||||||
|
# PING ['PING'] us.boogernet.org.
|
||||||
|
# PONG ['irc.foonet.com', 'PONG', 'irc.foonet.com'] 1114199424
|
||||||
|
addl = (text,)
|
||||||
|
elif op in ("NICK",):
|
||||||
|
# NICK ['brad!~brad@10.168.2.33', 'NICK'] bradaway
|
||||||
|
#
|
||||||
|
# The sender is the new nickname here, in case you want to
|
||||||
|
# send something to the sender.
|
||||||
|
|
||||||
|
# Apparently there are two different standards for this
|
||||||
|
# command.
|
||||||
|
if text:
|
||||||
|
sender = recipient(self, text)
|
||||||
|
else:
|
||||||
|
sender = recipient(self, args[2])
|
||||||
|
addl = (unpack_nuhost(args)[0],)
|
||||||
|
elif op in ("INVITE",):
|
||||||
|
forum = recipient(self, text)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
int(op)
|
||||||
|
except ValueError:
|
||||||
|
self.dbg("WARNING: unknown server code: %s" % op)
|
||||||
|
addl = tuple(args[3:]) + (text,)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.handle_cooked(op, sender, forum, addl)
|
||||||
|
except SystemExit:
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
self.err(sys.exc_info())
|
||||||
|
|
||||||
|
def handle_cooked(self, op, sender, forum, addl):
|
||||||
|
try:
|
||||||
|
func = getattr(self, 'cmd_' + op.lower())
|
||||||
|
except AttributeError:
|
||||||
|
self.unhandled(op, sender, forum, addl)
|
||||||
|
return
|
||||||
|
func(sender, forum, addl)
|
||||||
|
|
||||||
|
def cmd_ping(self, sender, forum, addl):
|
||||||
|
self.write('PONG', addl)
|
||||||
|
|
||||||
|
def unhandled(self, op, sender, forum, addl):
|
||||||
|
"""Handle all the stuff that had no handler.
|
||||||
|
|
||||||
|
This is a special handler in that it also gets the server code
|
||||||
|
as the first argument.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.dbg("unhandled: %s" % ((op, sender, forum, addl),))
|
||||||
|
|
||||||
|
|
||||||
|
class Bot(SmartIRCHandler):
|
||||||
|
"""A simple bot.
|
||||||
|
|
||||||
|
This automatically joins the channels you pass to the constructor,
|
||||||
|
tries to use one of the nicks provided, and reconnects if it gets
|
||||||
|
booted. You can use this as a base for more sophisticated bots.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, host, nicks, gecos, channels):
|
||||||
|
self.nicks = nicks
|
||||||
|
self.channels = channels
|
||||||
|
self.waiting = True
|
||||||
|
self._spool = []
|
||||||
|
SmartIRCHandler.__init__(self, host, nicks[0], gecos)
|
||||||
|
|
||||||
|
def despool(self, target, lines):
|
||||||
|
"""Slowly despool a bunch of lines to a target
|
||||||
|
|
||||||
|
Since the IRC server will block all output if we send it too
|
||||||
|
fast, use this to send large multi-line responses.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._spool.append((target, list(lines)))
|
||||||
|
|
||||||
|
def heartbeat(self):
|
||||||
|
SmartIRCHandler.heartbeat(self)
|
||||||
|
|
||||||
|
# Despool data
|
||||||
|
if self._spool:
|
||||||
|
# Take the first one on the queue, and put it on the end
|
||||||
|
which = self._spool[0]
|
||||||
|
del self._spool[0]
|
||||||
|
self._spool.append(which)
|
||||||
|
|
||||||
|
# Despool a line
|
||||||
|
target, lines = which
|
||||||
|
line = lines[0]
|
||||||
|
target.msg(line)
|
||||||
|
del lines[0]
|
||||||
|
if not lines:
|
||||||
|
self._spool.remove((target, lines))
|
||||||
|
|
||||||
|
def announce(self, text):
|
||||||
|
for c in self.channels:
|
||||||
|
self.write(['PRIVMSG', c], text)
|
||||||
|
|
||||||
|
def err(self, exception):
|
||||||
|
SmartIRCHandler.err(self, exception)
|
||||||
|
self.announce('*bzert*')
|
||||||
|
|
||||||
|
def cmd_001(self, sender, forum, addl):
|
||||||
|
for c in self.channels:
|
||||||
|
self.write('JOIN', c)
|
||||||
|
|
||||||
|
def writable(self):
|
||||||
|
if not self.waiting:
|
||||||
|
return asynchat.async_chat.writable(self)
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def write(self, *args):
|
||||||
|
SmartIRCHandler.write(self, *args)
|
||||||
|
|
||||||
|
def close(self, final=False):
|
||||||
|
SmartIRCHandler.close(self)
|
||||||
|
if not final:
|
||||||
|
self.dbg("Connection closed, reconnecting...")
|
||||||
|
self.waiting = True
|
||||||
|
self.connected = 0
|
||||||
|
# Wait a bit and reconnect
|
||||||
|
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self.add_timer(23, lambda : self.connect(self.host))
|
||||||
|
|
||||||
|
|
||||||
|
def handle_close(self):
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Miscellaneous IRC functions
|
||||||
|
##
|
||||||
|
|
||||||
|
ucletters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ[]\\"
|
||||||
|
lcletters = "abcdefghijklmnopqrstuvwxyz{}|"
|
||||||
|
|
||||||
|
ultrans = string.maketrans(ucletters, lcletters)
|
||||||
|
lutrans = string.maketrans(lcletters, ucletters)
|
||||||
|
casetrans = string.maketrans(''.join([ucletters, lcletters]),
|
||||||
|
''.join([lcletters, ucletters]))
|
||||||
|
|
||||||
|
def upper(s):
|
||||||
|
"""Convert a string to upper case.
|
||||||
|
|
||||||
|
Because IRC was developed in a nordic country, there are three extra
|
||||||
|
letters. In order to do case conversions properly, you need to use
|
||||||
|
the IRC-specific functions.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
return string.translate(s, lutrans)
|
||||||
|
|
||||||
|
def lower(s):
|
||||||
|
"""Convert a string to lower case
|
||||||
|
|
||||||
|
Because IRC was developed in a nordic country, there are three extra
|
||||||
|
letters. In order to do case conversions properly, you need to use
|
||||||
|
the IRC-specific functions.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
return string.translate(s, ultrans)
|
||||||
|
|
||||||
|
def swapcase(s):
|
||||||
|
"""Invert a string's case
|
||||||
|
|
||||||
|
Because IRC was developed in a nordic country, there are three extra
|
||||||
|
letters. In order to do case conversions properly, you need to use
|
||||||
|
the IRC-specific functions.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
return string.translate(s, casetrans)
|
||||||
|
|
||||||
|
|
||||||
|
def strcmp(s1, s2):
|
||||||
|
"""Case-insensitively compare two strings
|
||||||
|
|
||||||
|
Because IRC was developed in a nordic country, there are three extra
|
||||||
|
letters. In order to do case-insensitive comparisons properly, you
|
||||||
|
need to use this IRC-specific function.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
return (lower(s1) == lower(s2))
|
||||||
|
|
||||||
|
|
||||||
|
def unpack_nuhost(nuhost):
|
||||||
|
"""Unpack nick!user@host
|
||||||
|
|
||||||
|
Frequently, the first argument in a server message is in
|
||||||
|
nick!user@host format. You can just pass your whole argument list
|
||||||
|
to this function and get back a tuple containing:
|
||||||
|
|
||||||
|
(nick, user, host)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
[nick, uhost] = string.split(nuhost[0], '!', 1)
|
||||||
|
[user, host] = string.split(uhost, '@', 1)
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError, "not in nick!user@host format"
|
||||||
|
return (nick, user, host)
|
||||||
|
|
||||||
|
def run_forever(timeout=2.0):
|
||||||
|
"""Run your clients forever.
|
||||||
|
|
||||||
|
Just a handy front-end to asyncore.loop, so you don't have to import
|
||||||
|
asyncore yourself.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if False:
|
||||||
|
map = asyncore.socket_map
|
||||||
|
while map:
|
||||||
|
print map
|
||||||
|
asyncore.poll(timeout, map)
|
||||||
|
else:
|
||||||
|
asyncore.loop(timeout)
|
|
@ -0,0 +1,96 @@
|
||||||
|
#! /usr/bin/env python
|
||||||
|
|
||||||
|
import irc
|
||||||
|
|
||||||
|
NAME = ['arsenic']
|
||||||
|
INFO = "I'm a little teapot, short and stout"
|
||||||
|
|
||||||
|
class MultiChannel(irc.Channel):
|
||||||
|
"""Multiple-channel recipient
|
||||||
|
|
||||||
|
The idea is that this object can represent multiple channels, so
|
||||||
|
when it's told to do something, it will happen in more than one
|
||||||
|
place.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, ifchans, name):
|
||||||
|
self._ifchans = ifchans
|
||||||
|
self._name = name
|
||||||
|
|
||||||
|
def cmd(self, cmd, text):
|
||||||
|
for iface, chans in self._ifchans:
|
||||||
|
for chan in chans:
|
||||||
|
iface.write([cmd, chan], text)
|
||||||
|
|
||||||
|
|
||||||
|
class LinkBot(irc.Bot):
|
||||||
|
"""Linkbot stuff.
|
||||||
|
|
||||||
|
The strategy here is to relay messages to the
|
||||||
|
others, then get the others to act as if they had just seen the
|
||||||
|
message from their server.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *data):
|
||||||
|
self.others = []
|
||||||
|
self.fora = None
|
||||||
|
if data:
|
||||||
|
irc.Bot.__init__(self, *data)
|
||||||
|
|
||||||
|
def handle_cooked(self, op, sender, forum, addl):
|
||||||
|
"""The crux of the linkbot.
|
||||||
|
|
||||||
|
By replacing forum with a multi-channel forum, forum-directed
|
||||||
|
replies go to all channels.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.fora and forum and forum.is_channel():
|
||||||
|
forum = MultiChannel(self.fora, forum.name())
|
||||||
|
irc.Bot.handle_cooked(self, op, sender, forum, addl)
|
||||||
|
|
||||||
|
def set_others(self, others):
|
||||||
|
self.others = others
|
||||||
|
self.fora = []
|
||||||
|
for i in [self] + others:
|
||||||
|
self.fora.append((i, i.channels))
|
||||||
|
|
||||||
|
def broadcast(self, text):
|
||||||
|
for i in self.others:
|
||||||
|
i.announce(text)
|
||||||
|
|
||||||
|
def cmd_privmsg(self, sender, forum, addl):
|
||||||
|
if forum.is_channel():
|
||||||
|
self.broadcast('<%s> %s' % (sender.name(), addl[0]))
|
||||||
|
|
||||||
|
def cmd_cprivmsg(self, sender, forum, addl):
|
||||||
|
if forum.is_channel():
|
||||||
|
cmd = addl[0]
|
||||||
|
text = ' '.join(addl[1:])
|
||||||
|
if cmd == 'ACTION':
|
||||||
|
self.broadcast('* %s %s' % (sender.name(), text))
|
||||||
|
|
||||||
|
def cmd_nick(self, sender, forum, addl):
|
||||||
|
self.broadcast(' *** %s is now known as %s' % (addl[0], sender.name()))
|
||||||
|
|
||||||
|
def cmd_join(self, sender, forum, addl):
|
||||||
|
self.broadcast(' *** %s has joined' % (sender.name()))
|
||||||
|
|
||||||
|
def cmd_part(self, sender, forum, addl):
|
||||||
|
self.broadcast(' *** %s has left' % (sender.name()))
|
||||||
|
cmd_quit = cmd_part
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
l1 = LinkBot(('209.67.60.33', 6667),
|
||||||
|
NAME,
|
||||||
|
INFO,
|
||||||
|
['#disney'])
|
||||||
|
l2 = LinkBot(('woozle.org', 6667),
|
||||||
|
NAME,
|
||||||
|
INFO,
|
||||||
|
['#woozle'])
|
||||||
|
l1.set_others([l2])
|
||||||
|
l2.set_others([l1])
|
||||||
|
irc.run_forever()
|
|
@ -0,0 +1,38 @@
|
||||||
|
#! /usr/bin/env python
|
||||||
|
|
||||||
|
"""OpBot -- Hands out channel ops
|
||||||
|
|
||||||
|
This bot joins every channel on the server, and if opped in a channel
|
||||||
|
will op anyone who joins. It will poll the server for a channel list
|
||||||
|
and join any new channels as they appear. Once it has joined, it never
|
||||||
|
leaves a channel.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import irc
|
||||||
|
|
||||||
|
class NopBot(irc.Bot):
|
||||||
|
#debug = True
|
||||||
|
heartbeat_interval = 60
|
||||||
|
|
||||||
|
def cmd_001(self, sender, forum, addl):
|
||||||
|
irc.Bot.cmd_001(self, sender, forum, addl)
|
||||||
|
self.write(['LIST'])
|
||||||
|
|
||||||
|
def cmd_322(self, sender, forum, addl):
|
||||||
|
self.write(['JOIN', addl[0]])
|
||||||
|
|
||||||
|
def cmd_join(self, sender, forum, addl):
|
||||||
|
if sender.name() == self.nick:
|
||||||
|
forum.notice('If you op me, I will op everyone who joins this channel.')
|
||||||
|
forum.write(['MODE', forum.name(), '+o'], sender.name())
|
||||||
|
|
||||||
|
def heartbeat(self):
|
||||||
|
irc.Bot.heartbeat(self)
|
||||||
|
self.write(['LIST'])
|
||||||
|
|
||||||
|
n = NopBot(('woozle.org', 6667),
|
||||||
|
['OpBot'],
|
||||||
|
'Op me!',
|
||||||
|
[])
|
||||||
|
irc.run_forever()
|
|
@ -0,0 +1,74 @@
|
||||||
|
#! /usr/bin/env python
|
||||||
|
|
||||||
|
import os
|
||||||
|
import irc
|
||||||
|
import async_proc
|
||||||
|
|
||||||
|
class Runner(async_proc.process_dispatcher):
|
||||||
|
def __init__(self, cmdline, outfunc):
|
||||||
|
f = os.popen('%s 2>&1' % (cmdline), 'r')
|
||||||
|
self.outfunc = outfunc
|
||||||
|
self.linebuf = ""
|
||||||
|
async_proc.process_dispatcher.__init__(self, f)
|
||||||
|
|
||||||
|
def handle_read(self):
|
||||||
|
self.linebuf += self.recv(4098)
|
||||||
|
|
||||||
|
def handle_close(self):
|
||||||
|
ret = self.close()
|
||||||
|
if self.linebuf:
|
||||||
|
self.outfunc(self.linebuf, ret)
|
||||||
|
|
||||||
|
|
||||||
|
def esc(arg):
|
||||||
|
"Shell-escape an argument"
|
||||||
|
|
||||||
|
return "'" + arg.replace("'", "'\''") + "'"
|
||||||
|
|
||||||
|
|
||||||
|
def lesc(args):
|
||||||
|
"Shell-escape a list of arguments"
|
||||||
|
|
||||||
|
return [esc(arg) for arg in args]
|
||||||
|
|
||||||
|
|
||||||
|
class ProcBot(irc.Bot):
|
||||||
|
maxlines = 5
|
||||||
|
|
||||||
|
def proc_cb(self, pfx, sender, forum, linebuf, ret):
|
||||||
|
if not pfx:
|
||||||
|
pfx = ""
|
||||||
|
lines = []
|
||||||
|
for line in linebuf.split('\n'):
|
||||||
|
line = line.strip()
|
||||||
|
if line:
|
||||||
|
lines.append("%s%s" % (pfx, line))
|
||||||
|
if ret and not lines:
|
||||||
|
lines = ["%sThat generates an error (%d)." % (pfx, ret)]
|
||||||
|
if len(lines) > self.maxlines:
|
||||||
|
forum.msg("%sToo many lines, sending privately" % pfx)
|
||||||
|
self.despool(sender, lines)
|
||||||
|
else:
|
||||||
|
self.despool(forum, lines)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import bindingsbot
|
||||||
|
import re
|
||||||
|
|
||||||
|
class LsBot(ProcBot, bindingsbot.BindingsBot):
|
||||||
|
bindings = bindingsbot.BindingsBot.bindings
|
||||||
|
|
||||||
|
def ls(self, sender, forum, addl, match):
|
||||||
|
r = Runner('ls', lambda linebuf, ret: self.proc_cb("ls: ",
|
||||||
|
sender, forum,
|
||||||
|
linebuf, ret))
|
||||||
|
bindings.append((re.compile(r"^ls", re.IGNORECASE),
|
||||||
|
ls))
|
||||||
|
|
||||||
|
|
||||||
|
p = LsBot(('irc.woozle.org', 6667),
|
||||||
|
'procbot',
|
||||||
|
'hi asl',
|
||||||
|
["#ch"])
|
||||||
|
irc.run_forever()
|
|
@ -0,0 +1,146 @@
|
||||||
|
#! /usr/bin/env python
|
||||||
|
|
||||||
|
import cdb
|
||||||
|
import random
|
||||||
|
import codecs
|
||||||
|
|
||||||
|
_encode = codecs.getencoder('utf-8')
|
||||||
|
(_encode, _decode, _, _) = codecs.lookup('utf-8')
|
||||||
|
def encode(str):
|
||||||
|
return _encode(str)[0]
|
||||||
|
|
||||||
|
def decode(str):
|
||||||
|
return _decode(str)[0]
|
||||||
|
|
||||||
|
class Locked(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class SeedyB:
|
||||||
|
"""firebot-specific database using cdb.
|
||||||
|
|
||||||
|
Why CDB? Because everything else keeps going corrupt.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
* This doesn't preserve unsynced additions. If you crash before
|
||||||
|
running sync(), you lose.
|
||||||
|
* If you set a value to [], is is effectively deleted.
|
||||||
|
* You can lock something that's not in the database. You might
|
||||||
|
want to do this for words like 'that', so the bot doesn't pick
|
||||||
|
up on them.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, filename):
|
||||||
|
self.filename = filename
|
||||||
|
self.tempfile = "%s.tmp" % filename
|
||||||
|
|
||||||
|
self.db = {}
|
||||||
|
try:
|
||||||
|
self.cdb = cdb.init(self.filename)
|
||||||
|
except cdb.error:
|
||||||
|
d = cdb.cdbmake(self.filename, self.tempfile)
|
||||||
|
d.finish()
|
||||||
|
del d
|
||||||
|
self.cdb = cdb.init(self.filename)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.sync(force=True)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.cdb) + len(self.db)
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
self.delete(key)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
val = self.get(key)
|
||||||
|
if val is None:
|
||||||
|
raise KeyError(key)
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
return (key in self.db) or (key in self.cdb)
|
||||||
|
|
||||||
|
def length(self):
|
||||||
|
return (len(self.cdb), len(self.db))
|
||||||
|
|
||||||
|
def sync(self, force=False):
|
||||||
|
if not self.db:
|
||||||
|
return
|
||||||
|
|
||||||
|
tmp = cdb.cdbmake(self.filename, self.tempfile)
|
||||||
|
|
||||||
|
# Copy original
|
||||||
|
r = self.cdb.each()
|
||||||
|
while r:
|
||||||
|
k,v = r
|
||||||
|
dk = decode(k)
|
||||||
|
if k not in self.db:
|
||||||
|
tmp.add(*r)
|
||||||
|
r = self.cdb.each()
|
||||||
|
|
||||||
|
# Add new stuff
|
||||||
|
for k,l in self.db.iteritems():
|
||||||
|
for v in l:
|
||||||
|
try:
|
||||||
|
tmp.add(k,v)
|
||||||
|
except:
|
||||||
|
print (k,v)
|
||||||
|
raise
|
||||||
|
|
||||||
|
tmp.finish()
|
||||||
|
self.cdb = cdb.init(self.filename)
|
||||||
|
self.db = {}
|
||||||
|
|
||||||
|
def getall(self, key, special=None):
|
||||||
|
"""Return all values for a key"""
|
||||||
|
|
||||||
|
if special:
|
||||||
|
key = '\016%s:%s\017' % (special, key)
|
||||||
|
ekey = encode(key)
|
||||||
|
vals = self.db.get(ekey, None)
|
||||||
|
if vals is None:
|
||||||
|
vals = self.cdb.getall(ekey)
|
||||||
|
return [decode(v) for v in vals]
|
||||||
|
|
||||||
|
def get(self, key, default=None, special=None):
|
||||||
|
"""Get a value at random"""
|
||||||
|
|
||||||
|
vals = self.getall(key, special)
|
||||||
|
if vals:
|
||||||
|
return random.choice(vals)
|
||||||
|
else:
|
||||||
|
return default
|
||||||
|
|
||||||
|
def set(self, key, val, special=None):
|
||||||
|
if special:
|
||||||
|
key = '\016%s:%s\017' % (special, key)
|
||||||
|
ekey = encode(key)
|
||||||
|
if type(val) not in (type([]), type(())):
|
||||||
|
val = [val]
|
||||||
|
if not special and self.is_locked(key):
|
||||||
|
raise Locked()
|
||||||
|
self.db[ekey] = [encode(v) for v in val]
|
||||||
|
|
||||||
|
def delete(self, key, special=None):
|
||||||
|
val = self.get(key, special=special)
|
||||||
|
if not val:
|
||||||
|
raise KeyError(key)
|
||||||
|
self.set(key, [], special)
|
||||||
|
|
||||||
|
##
|
||||||
|
## Locking
|
||||||
|
##
|
||||||
|
|
||||||
|
def lock(self, key):
|
||||||
|
self.set(key, [''], special='lock')
|
||||||
|
|
||||||
|
def unlock(self, key):
|
||||||
|
self.set(key, [], special='lock')
|
||||||
|
|
||||||
|
def is_locked(self, key):
|
||||||
|
l = self.get(key, special='lock')
|
||||||
|
if l:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
open = SeedyB
|
|
@ -0,0 +1,325 @@
|
||||||
|
#! /usr/bin/env python2.2
|
||||||
|
|
||||||
|
import asyncore
|
||||||
|
import asynchat
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
__version__ = '1.0'
|
||||||
|
|
||||||
|
DEFAULT_ERROR_MESSAGE = """\
|
||||||
|
<head>
|
||||||
|
<title>Error response</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Error response</h1>
|
||||||
|
<p>Error code %(code)d.
|
||||||
|
<p>Message: %(message)s.
|
||||||
|
<p>Error code explanation: %(code)s = %(explain)s.
|
||||||
|
</body>
|
||||||
|
"""
|
||||||
|
|
||||||
|
URLS = []
|
||||||
|
|
||||||
|
class URLServer(asyncore.dispatcher):
|
||||||
|
def __init__(self, bind, connFactory):
|
||||||
|
asyncore.dispatcher.__init__(self)
|
||||||
|
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self.set_reuse_addr()
|
||||||
|
self.bind(bind)
|
||||||
|
self.listen(4)
|
||||||
|
self.connFactory = connFactory
|
||||||
|
|
||||||
|
def handle_accept(self):
|
||||||
|
conn, addr = self.accept()
|
||||||
|
self.connFactory(conn)
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPHandler(asynchat.async_chat):
|
||||||
|
def __init__(self, conn):
|
||||||
|
asynchat.async_chat.__init__(self, conn=conn)
|
||||||
|
self.client_address = self.getpeername()
|
||||||
|
self.set_terminator('\r\n\r\n')
|
||||||
|
self.data = ''
|
||||||
|
|
||||||
|
def collect_incoming_data(self, data):
|
||||||
|
self.data += data
|
||||||
|
|
||||||
|
def found_terminator(self):
|
||||||
|
try:
|
||||||
|
self.headers = self.data.split('\r\n')
|
||||||
|
self.requestline = self.headers[0]
|
||||||
|
self.command, self.path, self.request_version = self.requestline.split()
|
||||||
|
except:
|
||||||
|
self.send_error(500)
|
||||||
|
raise
|
||||||
|
|
||||||
|
try:
|
||||||
|
func = getattr(self, "do_" + self.command)
|
||||||
|
except AttributeError:
|
||||||
|
self.send_error(501, "Unsupported method (%s)" % `self.command`)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
func()
|
||||||
|
except:
|
||||||
|
self.send_error(500)
|
||||||
|
raise
|
||||||
|
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
|
# The Python system version, truncated to its first component.
|
||||||
|
sys_version = "Python/" + sys.version.split()[0]
|
||||||
|
|
||||||
|
# The server software version. You may want to override this.
|
||||||
|
# The format is multiple whitespace-separated strings,
|
||||||
|
# where each string is of the form name[/version].
|
||||||
|
server_version = "AsyncoreBaseHTTP/" + __version__
|
||||||
|
|
||||||
|
# The version of the HTTP protocol we support.
|
||||||
|
# Don't override unless you know what you're doing (hint: incoming
|
||||||
|
# requests are required to have exactly this version string).
|
||||||
|
protocol_version = "HTTP/1.0"
|
||||||
|
|
||||||
|
# Table mapping response codes to messages; entries have the
|
||||||
|
# form {code: (shortmessage, longmessage)}.
|
||||||
|
# See http://www.w3.org/hypertext/WWW/Protocols/HTTP/HTRESP.html
|
||||||
|
responses = {
|
||||||
|
200: ('OK', 'Request fulfilled, document follows'),
|
||||||
|
201: ('Created', 'Document created, URL follows'),
|
||||||
|
202: ('Accepted',
|
||||||
|
'Request accepted, processing continues off-line'),
|
||||||
|
203: ('Partial information', 'Request fulfilled from cache'),
|
||||||
|
204: ('No response', 'Request fulfilled, nothing follows'),
|
||||||
|
|
||||||
|
301: ('Moved', 'Object moved permanently -- see URI list'),
|
||||||
|
302: ('Found', 'Object moved temporarily -- see URI list'),
|
||||||
|
303: ('Method', 'Object moved -- see Method and URL list'),
|
||||||
|
304: ('Not modified',
|
||||||
|
'Document has not changed singe given time'),
|
||||||
|
|
||||||
|
400: ('Bad request',
|
||||||
|
'Bad request syntax or unsupported method'),
|
||||||
|
401: ('Unauthorized',
|
||||||
|
'No permission -- see authorization schemes'),
|
||||||
|
402: ('Payment required',
|
||||||
|
'No payment -- see charging schemes'),
|
||||||
|
403: ('Forbidden',
|
||||||
|
'Request forbidden -- authorization will not help'),
|
||||||
|
404: ('Not found', 'Nothing matches the given URI'),
|
||||||
|
|
||||||
|
500: ('Internal error', 'Server got itself in trouble'),
|
||||||
|
501: ('Not implemented',
|
||||||
|
'Server does not support this operation'),
|
||||||
|
502: ('Service temporarily overloaded',
|
||||||
|
'The server cannot process the request due to a high load'),
|
||||||
|
503: ('Gateway timeout',
|
||||||
|
'The gateway server did not receive a timely response'),
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
error_message_format = DEFAULT_ERROR_MESSAGE
|
||||||
|
|
||||||
|
def send_error(self, code, message=None):
|
||||||
|
"""Send and log an error reply.
|
||||||
|
|
||||||
|
Arguments are the error code, and a detailed message.
|
||||||
|
The detailed message defaults to the short entry matching the
|
||||||
|
response code.
|
||||||
|
|
||||||
|
This sends an error response (so it must be called before any
|
||||||
|
output has been generated), logs the error, and finally sends
|
||||||
|
a piece of HTML explaining the error to the user.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
short, long = self.responses[code]
|
||||||
|
except KeyError:
|
||||||
|
short, long = '???', '???'
|
||||||
|
if not message:
|
||||||
|
message = short
|
||||||
|
explain = long
|
||||||
|
self.log_error("code %d, message %s", code, message)
|
||||||
|
self.send_response(code, message)
|
||||||
|
self.send_header("Content-Type", "text/html")
|
||||||
|
self.end_headers()
|
||||||
|
self.send(self.error_message_format %
|
||||||
|
{'code': code,
|
||||||
|
'message': message,
|
||||||
|
'explain': explain})
|
||||||
|
|
||||||
|
def send_response(self, code, message=None):
|
||||||
|
"""Send the response header and log the response code.
|
||||||
|
|
||||||
|
Also send two standard headers with the server software
|
||||||
|
version and the current date.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.log_request(code)
|
||||||
|
if message is None:
|
||||||
|
if self.responses.has_key(code):
|
||||||
|
message = self.responses[code][0]
|
||||||
|
else:
|
||||||
|
message = ''
|
||||||
|
if self.request_version != 'HTTP/0.9':
|
||||||
|
self.send("%s %s %s\r\n" %
|
||||||
|
(self.protocol_version, str(code), message))
|
||||||
|
self.send_header('Server', self.version_string())
|
||||||
|
self.send_header('Date', self.date_time_string())
|
||||||
|
|
||||||
|
def send_header(self, keyword, value):
|
||||||
|
"""Send a MIME header."""
|
||||||
|
if self.request_version != 'HTTP/0.9':
|
||||||
|
self.send("%s: %s\r\n" % (keyword, value))
|
||||||
|
|
||||||
|
def end_headers(self):
|
||||||
|
"""Send the blank line ending the MIME headers."""
|
||||||
|
if self.request_version != 'HTTP/0.9':
|
||||||
|
self.send("\r\n")
|
||||||
|
|
||||||
|
def log_request(self, code='-', size='-'):
|
||||||
|
"""Log an accepted request.
|
||||||
|
|
||||||
|
This is called by send_reponse().
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.log_message('"%s" %s %s',
|
||||||
|
self.requestline, str(code), str(size))
|
||||||
|
|
||||||
|
def log_error(self, *args):
|
||||||
|
"""Log an error.
|
||||||
|
|
||||||
|
This is called when a request cannot be fulfilled. By
|
||||||
|
default it passes the message on to log_message().
|
||||||
|
|
||||||
|
Arguments are the same as for log_message().
|
||||||
|
|
||||||
|
XXX This should go to the separate error log.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
apply(self.log_message, args)
|
||||||
|
|
||||||
|
def log_message(self, format, *args):
|
||||||
|
"""Log an arbitrary message.
|
||||||
|
|
||||||
|
This is used by all other logging functions. Override
|
||||||
|
it if you have specific logging wishes.
|
||||||
|
|
||||||
|
The first argument, FORMAT, is a format string for the
|
||||||
|
message to be logged. If the format string contains
|
||||||
|
any % escapes requiring parameters, they should be
|
||||||
|
specified as subsequent arguments (it's just like
|
||||||
|
printf!).
|
||||||
|
|
||||||
|
The client host and current date/time are prefixed to
|
||||||
|
every message.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
return
|
||||||
|
sys.stderr.write("%s - - [%s] %s\n" %
|
||||||
|
(self.address_string(),
|
||||||
|
self.log_date_time_string(),
|
||||||
|
format%args))
|
||||||
|
|
||||||
|
def version_string(self):
|
||||||
|
"""Return the server software version string."""
|
||||||
|
return self.server_version + ' ' + self.sys_version
|
||||||
|
|
||||||
|
def date_time_string(self):
|
||||||
|
"""Return the current date and time formatted for a message header."""
|
||||||
|
now = time.time()
|
||||||
|
year, month, day, hh, mm, ss, wd, y, z = time.gmtime(now)
|
||||||
|
s = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
|
||||||
|
self.weekdayname[wd],
|
||||||
|
day, self.monthname[month], year,
|
||||||
|
hh, mm, ss)
|
||||||
|
return s
|
||||||
|
|
||||||
|
def log_date_time_string(self):
|
||||||
|
"""Return the current time formatted for logging."""
|
||||||
|
now = time.time()
|
||||||
|
year, month, day, hh, mm, ss, x, y, z = time.localtime(now)
|
||||||
|
s = "%02d/%3s/%04d %02d:%02d:%02d" % (
|
||||||
|
day, self.monthname[month], year, hh, mm, ss)
|
||||||
|
return s
|
||||||
|
|
||||||
|
weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
||||||
|
|
||||||
|
monthname = [None,
|
||||||
|
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
||||||
|
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
||||||
|
|
||||||
|
def address_string(self):
|
||||||
|
"""Return the client address formatted for logging.
|
||||||
|
|
||||||
|
This version looks up the full hostname using gethostbyaddr(),
|
||||||
|
and tries to find a name that contains at least one dot.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
host, port = self.client_address
|
||||||
|
return socket.getfqdn(host)
|
||||||
|
|
||||||
|
|
||||||
|
class URLHandler(HTTPHandler):
|
||||||
|
def do_GET(self):
|
||||||
|
global URLS
|
||||||
|
|
||||||
|
if self.path == '/':
|
||||||
|
self.list_urls()
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
idx = int(self.path[1:])
|
||||||
|
url = URLS[idx]
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
self.send_error(404)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.send_response(301)
|
||||||
|
self.send_header('Location', url)
|
||||||
|
self.send_header('Content-Type', 'text/html')
|
||||||
|
self.end_headers()
|
||||||
|
self.send('<a href="%s">%s</a>' % (url, url))
|
||||||
|
|
||||||
|
def log_message(self, format, *args):
|
||||||
|
# Don't do anything, so we can run in the background.
|
||||||
|
pass
|
||||||
|
|
||||||
|
def list_urls(self):
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-Type', 'text/html')
|
||||||
|
self.end_headers()
|
||||||
|
self.send('<title>URLs</title><h1>URLs</h1><ol>\n')
|
||||||
|
for url in URLS:
|
||||||
|
self.send('<li><a href="%s">%s</a></li>\n' % (url, url))
|
||||||
|
self.send('</ol>\n')
|
||||||
|
|
||||||
|
def add(url):
|
||||||
|
URLS.append(url)
|
||||||
|
return len(URLS) - 1
|
||||||
|
|
||||||
|
def unpackHost(str):
|
||||||
|
host, port = str.split(':')
|
||||||
|
port = int(port)
|
||||||
|
return (host, port)
|
||||||
|
|
||||||
|
def start(bindaddr):
|
||||||
|
return URLServer(bindaddr, URLHandler)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
import sys
|
||||||
|
|
||||||
|
(bind,) = sys.argv[1:]
|
||||||
|
bindaddr = unpackHost(bind)
|
||||||
|
m = start(bindaddr)
|
||||||
|
asyncore.loop()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -0,0 +1,22 @@
|
||||||
|
from firebot import FireBot
|
||||||
|
import irc
|
||||||
|
import re
|
||||||
|
|
||||||
|
class Gallium(FireBot):
|
||||||
|
#debug = True
|
||||||
|
bindings = []
|
||||||
|
|
||||||
|
bindings.extend(FireBot.bindings)
|
||||||
|
|
||||||
|
|
||||||
|
NICK = ['gallium']
|
||||||
|
INFO = "I'm a little printf, short and stdout"
|
||||||
|
HOSTS = [('woozle.org', 6667),
|
||||||
|
('209.67.60.33', 6667)]
|
||||||
|
|
||||||
|
l1 = Gallium(("woozle.org", 6667),
|
||||||
|
NICK,
|
||||||
|
INFO,
|
||||||
|
["#test"])
|
||||||
|
|
||||||
|
irc.run_forever()
|
|
@ -0,0 +1,75 @@
|
||||||
|
#! /usr/bin/env python
|
||||||
|
|
||||||
|
import asynchat
|
||||||
|
import adns
|
||||||
|
import urlparse
|
||||||
|
import socket
|
||||||
|
|
||||||
|
resolver = adns.init()
|
||||||
|
|
||||||
|
class WebRetriever(asynchat.async_chat):
|
||||||
|
def __init__(self, url, body_cb):
|
||||||
|
asynchat.async_chat.__init__(self)
|
||||||
|
self.body_cb = body_cb
|
||||||
|
self.url = url
|
||||||
|
(self.scheme,
|
||||||
|
self.netloc,
|
||||||
|
self.path,
|
||||||
|
self.query,
|
||||||
|
self.fragment) = urlparse.urlsplit(url)
|
||||||
|
assert self.scheme == 'http'
|
||||||
|
try:
|
||||||
|
self.host, port = self.netloc.split(':')
|
||||||
|
self.port = int(port)
|
||||||
|
except ValueError:
|
||||||
|
self.host = self.netloc
|
||||||
|
self.port = 80
|
||||||
|
self.set_terminator('\n')
|
||||||
|
self.in_headers = True
|
||||||
|
self.inbuf = ''
|
||||||
|
self.body = []
|
||||||
|
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self.dnsq = resolver.submit(self.host, adns.rr.A)
|
||||||
|
self.resolved = False
|
||||||
|
|
||||||
|
def readable(self):
|
||||||
|
if not self.resolved:
|
||||||
|
try:
|
||||||
|
self.resolved = self.dnsq.check()
|
||||||
|
self.connect((self.resolved[3][0], self.port))
|
||||||
|
except adns.NotReady:
|
||||||
|
return False
|
||||||
|
return asynchat.async_chat.readable(self)
|
||||||
|
|
||||||
|
def writable(self):
|
||||||
|
return self.resolved and asynchat.async_chat.writable(self)
|
||||||
|
|
||||||
|
def collect_incoming_data(self, data):
|
||||||
|
self.inbuf += data
|
||||||
|
|
||||||
|
def handle_connect(self):
|
||||||
|
path = urlparse.urlunsplit((None, None, self.path, self.query, self.fragment))
|
||||||
|
self.push('GET %s HTTP/1.0\r\n' % path)
|
||||||
|
self.push('Host: %s\r\n' % self.host)
|
||||||
|
self.push('\r\n')
|
||||||
|
|
||||||
|
def found_terminator(self):
|
||||||
|
data, self.inbuf = self.inbuf, ''
|
||||||
|
if self.in_headers:
|
||||||
|
if not data.strip():
|
||||||
|
self.in_headers = False
|
||||||
|
else:
|
||||||
|
self.body.append(data + self.get_terminator())
|
||||||
|
|
||||||
|
def handle_close(self):
|
||||||
|
asynchat.async_chat.close(self)
|
||||||
|
self.body_cb(self.body)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import asyncore
|
||||||
|
|
||||||
|
def p(data):
|
||||||
|
print ''.join(data)
|
||||||
|
|
||||||
|
e = WebRetriever('http://quote.yahoo.com/d/quotes.csv?s=wgrd&f=sl1d1t1c1ohgvj1pp2owern&e=.csv', p)
|
||||||
|
asyncore.loop()
|
Loading…
Reference in New Issue