mirror of https://github.com/nealey/firebot
Import from darcs
This commit is contained in:
commit
3e05fc5b4c
|
@ -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,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