Merged .showtell changes with tip.
This commit is contained in:
commit
20fd90b869
7
bot.py
7
bot.py
|
@ -18,7 +18,8 @@ bot = Bot()
|
||||||
print 'Loading plugins'
|
print 'Loading plugins'
|
||||||
|
|
||||||
# bootstrap the reloader
|
# bootstrap the reloader
|
||||||
eval(compile(open('core/reload.py', 'U').read(), 'core/reload.py', 'exec'))
|
eval(compile(open(os.path.join('core', 'reload.py'), 'U').read(),
|
||||||
|
os.path.join('core', 'reload.py'), 'exec'))
|
||||||
reload(init=True)
|
reload(init=True)
|
||||||
|
|
||||||
print 'Connecting to IRC'
|
print 'Connecting to IRC'
|
||||||
|
@ -32,12 +33,14 @@ try:
|
||||||
print 'ERROR: more than one connection named "%s"' % name
|
print 'ERROR: more than one connection named "%s"' % name
|
||||||
raise ValueError
|
raise ValueError
|
||||||
bot.conns[name] = irc(conf['server'], conf['nick'],
|
bot.conns[name] = irc(conf['server'], conf['nick'],
|
||||||
channels=conf['channels'], conf=conf)
|
port=conf.get('port', 6667), channels=conf['channels'], conf=conf)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
print 'ERROR: malformed config file', Exception, e
|
print 'ERROR: malformed config file', Exception, e
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
bot.persist_dir = os.path.abspath('persist')
|
bot.persist_dir = os.path.abspath('persist')
|
||||||
|
if not os.path.exists(bot.persist_dir):
|
||||||
|
os.mkdir(bot.persist_dir)
|
||||||
|
|
||||||
print 'Running main loop'
|
print 'Running main loop'
|
||||||
|
|
||||||
|
|
|
@ -4,14 +4,24 @@ from util import yaml
|
||||||
|
|
||||||
if not os.path.exists('config'):
|
if not os.path.exists('config'):
|
||||||
conf = {'connections': [
|
conf = {'connections': [
|
||||||
{'local irc': {'nick': 'skybot',
|
{'local irc':
|
||||||
'server': 'localhost',
|
{'nick': 'skybot',
|
||||||
'channels': ["#test"]}}]}
|
#'user': 'skybot',
|
||||||
|
#'realname': 'Python bot - http://bitbucket.org/Scaevolus/skybot/',
|
||||||
|
'server': 'localhost',
|
||||||
|
#'port': 6667,
|
||||||
|
'channels': ["#test"],
|
||||||
|
#'nickserv_password', 'password',
|
||||||
|
#'nickserv_name': 'nickserv',
|
||||||
|
#'nickserv_command': 'IDENTIFY %s'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]}
|
||||||
yaml.dump(conf, open('config', 'w'))
|
yaml.dump(conf, open('config', 'w'))
|
||||||
del conf
|
del conf
|
||||||
|
|
||||||
bot.config = yaml.load(open('config'))
|
bot.config = yaml.load(open('config'))
|
||||||
bot._config_dirty = False
|
bot._config_dirty = True # force a rewrite on start
|
||||||
bot._config_mtime = os.stat('config').st_mtime
|
bot._config_mtime = os.stat('config').st_mtime
|
||||||
|
|
||||||
def config_dirty(self):
|
def config_dirty(self):
|
||||||
|
|
|
@ -93,8 +93,9 @@ class irc(object):
|
||||||
self.conn = crlf_tcp(self.server, self.port)
|
self.conn = crlf_tcp(self.server, self.port)
|
||||||
thread.start_new_thread(self.conn.run, ())
|
thread.start_new_thread(self.conn.run, ())
|
||||||
self.set_nick(self.nick)
|
self.set_nick(self.nick)
|
||||||
self.cmd("USER", ["skybot", "3", "*",
|
self.cmd("USER",
|
||||||
":Python bot - http://bitbucket.org/Scaevolus/skybot/"])
|
[conf.get('user', 'skybot'), "3", "*", ':' + conf.get('realname',
|
||||||
|
'Python bot - http://bitbucket.org/Scaevolus/skybot/')])
|
||||||
|
|
||||||
def parse_loop(self):
|
def parse_loop(self):
|
||||||
while True:
|
while True:
|
||||||
|
|
18
core/main.py
18
core/main.py
|
@ -5,19 +5,19 @@ class Input(object):
|
||||||
|
|
||||||
def __init__(self, conn, raw, prefix, command,
|
def __init__(self, conn, raw, prefix, command,
|
||||||
params, nick, user, host, paraml, msg):
|
params, nick, user, host, paraml, msg):
|
||||||
self.conn = conn
|
self.conn = conn # irc object
|
||||||
self.server = conn.server
|
self.server = conn.server # hostname of server
|
||||||
self.raw = raw
|
self.raw = raw # unprocessed line of text
|
||||||
self.prefix = prefix
|
self.prefix = prefix # usually hostmask
|
||||||
self.command = command
|
self.command = command # PRIVMSG, JOIN, etc.
|
||||||
self.params = params
|
self.params = params
|
||||||
self.nick = nick
|
self.nick = nick
|
||||||
self.user = user
|
self.user = user # user@host
|
||||||
self.host = host
|
self.host = host
|
||||||
self.paraml = paraml
|
self.paraml = paraml # params[-1] without the :
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.chan = paraml[0]
|
self.chan = paraml[0]
|
||||||
if self.chan == conn.nick:
|
if self.chan == conn.nick: # is a PM
|
||||||
self.chan = nick
|
self.chan = nick
|
||||||
|
|
||||||
def say(self, msg):
|
def say(self, msg):
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import glob
|
|
||||||
import collections
|
import collections
|
||||||
import traceback
|
import glob
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
if 'mtimes' not in globals():
|
if 'mtimes' not in globals():
|
||||||
mtimes = {}
|
mtimes = {}
|
||||||
|
@ -19,7 +20,7 @@ def reload(init=False):
|
||||||
if init:
|
if init:
|
||||||
bot.plugs = collections.defaultdict(lambda: [])
|
bot.plugs = collections.defaultdict(lambda: [])
|
||||||
|
|
||||||
for filename in glob.glob("core/*.py"):
|
for filename in glob.glob(os.path.join("core", "*.py")):
|
||||||
mtime = os.stat(filename).st_mtime
|
mtime = os.stat(filename).st_mtime
|
||||||
if mtime != mtimes.get(filename):
|
if mtime != mtimes.get(filename):
|
||||||
mtimes[filename] = mtime
|
mtimes[filename] = mtime
|
||||||
|
@ -32,11 +33,11 @@ def reload(init=False):
|
||||||
sys.exit() # script on startup
|
sys.exit() # script on startup
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if filename == 'core/reload.py':
|
if filename == os.path.join('core', 'reload.py'):
|
||||||
reload(init=init)
|
reload(init=init)
|
||||||
return
|
return
|
||||||
|
|
||||||
fileset = set(glob.glob("plugins/*py"))
|
fileset = set(glob.glob(os.path.join('plugins', '*py')))
|
||||||
for name, data in bot.plugs.iteritems(): # remove deleted/moved plugins
|
for name, data in bot.plugs.iteritems(): # remove deleted/moved plugins
|
||||||
bot.plugs[name] = filter(lambda x: x[0][0] in fileset, data)
|
bot.plugs[name] = filter(lambda x: x[0][0] in fileset, data)
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ def nrolls(count, n):
|
||||||
def dice(inp):
|
def dice(inp):
|
||||||
".dice <diceroll> -- simulates dicerolls, e.g. .dice 2d20-d5+4 roll 2 " \
|
".dice <diceroll> -- simulates dicerolls, e.g. .dice 2d20-d5+4 roll 2 " \
|
||||||
"D20s, subtract 1D5, add 4"
|
"D20s, subtract 1D5, add 4"
|
||||||
if not inp.strip():
|
if not inp:
|
||||||
return dice.__doc__
|
return dice.__doc__
|
||||||
|
|
||||||
spec = whitespace_re.sub('', inp)
|
spec = whitespace_re.sub('', inp)
|
||||||
|
|
|
@ -6,7 +6,6 @@ from util import hook
|
||||||
@hook.command
|
@hook.command
|
||||||
def down(inp):
|
def down(inp):
|
||||||
'''.down <url> -- checks to see if the site is down'''
|
'''.down <url> -- checks to see if the site is down'''
|
||||||
inp = inp.strip()
|
|
||||||
|
|
||||||
if not inp:
|
if not inp:
|
||||||
return down.__doc__
|
return down.__doc__
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from util import hook
|
from util import hook
|
||||||
from pycparser.cdecl import explain_c_declaration
|
from pycparser.cdecl import explain_c_declaration
|
||||||
|
|
||||||
@hook.command('explain')
|
@hook.command
|
||||||
def explain(inp):
|
def explain(inp):
|
||||||
".explain <c expression> -- gives an explanation of C expression"
|
".explain <c expression> -- gives an explanation of C expression"
|
||||||
if not inp:
|
if not inp:
|
||||||
|
@ -10,6 +10,6 @@ def explain(inp):
|
||||||
inp = inp.encode('utf8', 'ignore')
|
inp = inp.encode('utf8', 'ignore')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return explain_c_declaration(inp.rstrip())
|
return explain_c_declaration(inp)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
return 'error: %s' % e
|
return 'error: %s' % e
|
||||||
|
|
|
@ -10,7 +10,7 @@ def help(bot, input):
|
||||||
if func.__doc__ is not None:
|
if func.__doc__ is not None:
|
||||||
funcs[csig[1]] = func
|
funcs[csig[1]] = func
|
||||||
|
|
||||||
if not input.inp.strip():
|
if not input.inp:
|
||||||
input.pm('available commands: ' + ' '.join(sorted(funcs)))
|
input.pm('available commands: ' + ' '.join(sorted(funcs)))
|
||||||
else:
|
else:
|
||||||
if input.inp in funcs:
|
if input.inp in funcs:
|
||||||
|
|
|
@ -33,6 +33,8 @@ irc_color_re = re.compile(r'(\x03(\d+,\d+|\d)|[\x0f\x02\x16\x1f])')
|
||||||
|
|
||||||
|
|
||||||
def get_log_filename(dir, server, chan):
|
def get_log_filename(dir, server, chan):
|
||||||
|
if chan.startswith(':'):
|
||||||
|
chan = chan[1:]
|
||||||
return os.path.join(dir, 'log', gmtime('%Y'), server,
|
return os.path.join(dir, 'log', gmtime('%Y'), server,
|
||||||
gmtime('%%s.%m-%d.log') % chan).lower()
|
gmtime('%%s.%m-%d.log') % chan).lower()
|
||||||
|
|
||||||
|
@ -53,7 +55,6 @@ def beautify(input):
|
||||||
args['msg'] = irc_color_re.sub('', args['msg'])
|
args['msg'] = irc_color_re.sub('', args['msg'])
|
||||||
|
|
||||||
if input.command == 'PRIVMSG' and input.msg.count('\x01') >= 2:
|
if input.command == 'PRIVMSG' and input.msg.count('\x01') >= 2:
|
||||||
#ctcp
|
|
||||||
ctcp = input.msg.split('\x01', 2)[1].split(' ', 1)
|
ctcp = input.msg.split('\x01', 2)[1].split(' ', 1)
|
||||||
if len(ctcp) == 1:
|
if len(ctcp) == 1:
|
||||||
ctcp += ['']
|
ctcp += ['']
|
||||||
|
|
|
@ -15,7 +15,7 @@ def py(inp):
|
||||||
return py.__doc__
|
return py.__doc__
|
||||||
|
|
||||||
res = urllib.urlopen("http://eval.appspot.com/eval?statement=%s" %
|
res = urllib.urlopen("http://eval.appspot.com/eval?statement=%s" %
|
||||||
urllib.quote(inp.strip(), safe='')).readlines()
|
urllib.quote(inp, safe='')).readlines()
|
||||||
if len(res) == 0:
|
if len(res) == 0:
|
||||||
return
|
return
|
||||||
res[0] = re_lineends.split(res[0])[0]
|
res[0] = re_lineends.split(res[0])[0]
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
import os
|
||||||
|
import sqlite3
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
|
||||||
|
from util import hook
|
||||||
|
|
||||||
|
dbname = "skybot.db"
|
||||||
|
|
||||||
|
def db_connect(db):
|
||||||
|
conn = sqlite3.connect(db)
|
||||||
|
conn.execute('''create table if not exists quotes
|
||||||
|
(server, chan, nick, add_nick, msg, time real, deleted default 0,
|
||||||
|
primary key (server, chan, nick, msg))''')
|
||||||
|
conn.commit()
|
||||||
|
return conn
|
||||||
|
|
||||||
|
def add_quote(conn, server, chan, nick, add_nick, msg):
|
||||||
|
now = time.time()
|
||||||
|
print repr((conn, server, add_nick, nick, msg, time))
|
||||||
|
conn.execute('''insert or fail into quotes (server, chan, nick, add_nick,
|
||||||
|
msg, time) values(?,?,?,?,?,?)''',
|
||||||
|
(server, chan, nick, add_nick, msg, now))
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
def get_quotes_by_nick(conn, server, chan, nick):
|
||||||
|
return conn.execute("select time, nick, msg from quotes where deleted!=1 "
|
||||||
|
"and server=? and chan=? and lower(nick)=lower(?) order by time",
|
||||||
|
(server, chan, nick)).fetchall()
|
||||||
|
|
||||||
|
def get_quotes_by_chan(conn, server, chan):
|
||||||
|
return conn.execute("select time, nick, msg from quotes where deleted!=1 "
|
||||||
|
"and server=? and chan=? order by time", (server, chan)).fetchall()
|
||||||
|
|
||||||
|
|
||||||
|
def format_quote(q, num, n_quotes):
|
||||||
|
ctime, nick, msg = q
|
||||||
|
return "[%d/%d] %s <%s> %s" % (num, n_quotes,
|
||||||
|
time.strftime("%Y-%m-%d", time.gmtime(ctime)), nick, msg)
|
||||||
|
|
||||||
|
|
||||||
|
@hook.command('q')
|
||||||
|
@hook.command
|
||||||
|
def quote(bot, input):
|
||||||
|
".q/.quote <nick/#chan> [#n]/.quote add <nick> <msg> -- gets " \
|
||||||
|
"random or [#n]th quote by <nick> or from <#chan>/adds quote"
|
||||||
|
|
||||||
|
dbpath = os.path.join(bot.persist_dir, dbname)
|
||||||
|
conn = db_connect(dbpath)
|
||||||
|
|
||||||
|
try:
|
||||||
|
add = re.match(r"add\s+<?[^\w]?(\S+?)>?\s+(.*)", input.inp, re.I)
|
||||||
|
retrieve = re.match(r"(\S+)(?:\s+#?(-?\d+))?", input.inp)
|
||||||
|
chan = input.chan
|
||||||
|
|
||||||
|
if add:
|
||||||
|
nick, msg = add.groups()
|
||||||
|
try:
|
||||||
|
add_quote(conn, input.server, chan, nick, input.nick, msg)
|
||||||
|
except sqlite3.IntegrityError:
|
||||||
|
return "message already stored, doing nothing."
|
||||||
|
return "quote added."
|
||||||
|
elif retrieve:
|
||||||
|
select, num = retrieve.groups()
|
||||||
|
|
||||||
|
by_chan = False
|
||||||
|
if select.startswith('#'):
|
||||||
|
by_chan = True
|
||||||
|
quotes = get_quotes_by_chan(conn, input.server, select)
|
||||||
|
else:
|
||||||
|
quotes = get_quotes_by_nick(conn, input.server, chan, select)
|
||||||
|
|
||||||
|
n_quotes = len(quotes)
|
||||||
|
|
||||||
|
if not n_quotes:
|
||||||
|
return "no quotes found"
|
||||||
|
|
||||||
|
if num:
|
||||||
|
num = int(num)
|
||||||
|
|
||||||
|
if num:
|
||||||
|
if num > n_quotes:
|
||||||
|
return "I only have %d quote%s for %s" % (n_quotes,
|
||||||
|
('s', '')[n_quotes == 1], select)
|
||||||
|
else:
|
||||||
|
selected_quote = quotes[num - 1]
|
||||||
|
else:
|
||||||
|
num = random.randint(1, n_quotes)
|
||||||
|
selected_quote = quotes[num - 1]
|
||||||
|
|
||||||
|
return format_quote(selected_quote, num, n_quotes)
|
||||||
|
else:
|
||||||
|
return quote.__doc__
|
||||||
|
finally:
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
|
@ -0,0 +1,38 @@
|
||||||
|
'''
|
||||||
|
regular.py
|
||||||
|
|
||||||
|
skybot plugin for testing regular expressions
|
||||||
|
by Ipsum
|
||||||
|
'''
|
||||||
|
|
||||||
|
import thread
|
||||||
|
import codecs
|
||||||
|
import re
|
||||||
|
|
||||||
|
from util import hook
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@hook.command('re')
|
||||||
|
def reg(bot, input):
|
||||||
|
".re <regex> <string> -- matches regular expression in given <string> (seperate regex and string by 2 spaces)"
|
||||||
|
|
||||||
|
m = ""
|
||||||
|
|
||||||
|
if len(input.msg) < 3:
|
||||||
|
return reg.__doc__
|
||||||
|
|
||||||
|
query = input.inp.partition(" ")
|
||||||
|
|
||||||
|
|
||||||
|
if query[2] != "":
|
||||||
|
r = re.compile(query[0])
|
||||||
|
|
||||||
|
matches = r.findall(query[2])
|
||||||
|
for match in matches:
|
||||||
|
m += match + "|"
|
||||||
|
|
||||||
|
return m.rstrip('|')
|
||||||
|
|
||||||
|
else:
|
||||||
|
return reg.__doc__
|
|
@ -46,14 +46,13 @@ def remember(bot, input):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return remember.__doc__
|
return remember.__doc__
|
||||||
|
|
||||||
tail = tail.strip()
|
|
||||||
low = head.lower()
|
low = head.lower()
|
||||||
if low not in memory[filename]:
|
if low not in memory[filename]:
|
||||||
input.reply("done.")
|
input.reply("done.")
|
||||||
else:
|
else:
|
||||||
input.reply('forgetting that "%s", remembering this instead.' %
|
input.reply('forgetting that "%s", remembering this instead.' %
|
||||||
memory[filename][low])
|
memory[filename][low])
|
||||||
memory[filename][low] = input.inp.strip()
|
memory[filename][low] = input.inp
|
||||||
save_memory(filename, memory[filename])
|
save_memory(filename, memory[filename])
|
||||||
|
|
||||||
|
|
||||||
|
@ -64,10 +63,10 @@ def forget(bot, input):
|
||||||
filename = make_filename(bot.persist_dir, input.chan)
|
filename = make_filename(bot.persist_dir, input.chan)
|
||||||
memory.setdefault(filename, load_memory(filename))
|
memory.setdefault(filename, load_memory(filename))
|
||||||
|
|
||||||
if not input.inp.strip():
|
if not input.inp:
|
||||||
return forget.__doc__
|
return forget.__doc__
|
||||||
|
|
||||||
low = input.inp.strip().lower()
|
low = input.inp.lower()
|
||||||
if low not in memory[filename]:
|
if low not in memory[filename]:
|
||||||
return "I don't know about that."
|
return "I don't know about that."
|
||||||
if not hasattr(input, 'chan'):
|
if not hasattr(input, 'chan'):
|
||||||
|
|
|
@ -40,7 +40,7 @@ def seen(bot, input):
|
||||||
if len(input.msg) < 6:
|
if len(input.msg) < 6:
|
||||||
return seen.__doc__
|
return seen.__doc__
|
||||||
|
|
||||||
query = input.inp.strip()
|
query = input.inp
|
||||||
|
|
||||||
if query.lower() == input.nick.lower():
|
if query.lower() == input.nick.lower():
|
||||||
return "Have you looked in a mirror lately?"
|
return "Have you looked in a mirror lately?"
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from util import hook
|
from util import hook
|
||||||
|
@ -16,14 +17,29 @@ def sieve_suite(bot, input, func, args):
|
||||||
hook = args.get('hook', r'(.*)')
|
hook = args.get('hook', r'(.*)')
|
||||||
|
|
||||||
if args.get('prefix', True):
|
if args.get('prefix', True):
|
||||||
# add a prefix, unless it's a private message
|
if input.chan == input.nick: # private message, prefix not required
|
||||||
hook = (r'^(?:[.!]|' if input.chan != input.nick else r'^(?:[.!]?|') \
|
prefix = r'^(?:[.!]?|'
|
||||||
+ input.conn.nick + r'[:,]*\s*)' + hook
|
else:
|
||||||
|
prefix = r'^(?:[.!]|'
|
||||||
|
hook = prefix + input.conn.nick + r'[:,]*\s)' + hook
|
||||||
|
|
||||||
input.re = re.match(hook, input.msg, flags=re.I)
|
input.re = re.match(hook, input.msg, flags=re.I)
|
||||||
if input.re is None:
|
if input.re is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
input.inp = ' '.join(input.re.groups())
|
acl = bot.config.get('acls', {}).get(func.__name__)
|
||||||
|
if acl:
|
||||||
|
print acl
|
||||||
|
if 'deny-except' in acl:
|
||||||
|
allowed_channels = map(str.lower, acl['deny-except'])
|
||||||
|
if input.chan.lower() not in allowed_channels:
|
||||||
|
return None
|
||||||
|
if 'allow-except' in acl:
|
||||||
|
denied_channels = map(str.lower, acl['allow-except'])
|
||||||
|
if input.chan.lower() in denied_channels:
|
||||||
|
return None
|
||||||
|
|
||||||
|
input.inp_unstripped = ' '.join(input.re.groups())
|
||||||
|
input.inp = input.inp_unstripped.strip()
|
||||||
|
|
||||||
return input
|
return input
|
||||||
|
|
|
@ -7,11 +7,12 @@ import json
|
||||||
from util import hook
|
from util import hook
|
||||||
|
|
||||||
@hook.command
|
@hook.command
|
||||||
def suggest(inp):
|
def suggest(bot, input):
|
||||||
".suggest [#n] <phrase> -- gets a random/the nth suggested google search"
|
".suggest [#n] <phrase> -- gets a random/the nth suggested google search"
|
||||||
if not inp.strip():
|
if not input.inp:
|
||||||
return suggest.__doc__
|
return suggest.__doc__
|
||||||
|
|
||||||
|
inp = input.inp_unstripped
|
||||||
m = re.match('^#(\d+) (.+)$', inp)
|
m = re.match('^#(\d+) (.+)$', inp)
|
||||||
if m:
|
if m:
|
||||||
num, inp = m.groups()
|
num, inp = m.groups()
|
||||||
|
|
|
@ -81,7 +81,7 @@ def tell(bot, input):
|
||||||
if len(input.msg) < 6:
|
if len(input.msg) < 6:
|
||||||
return tell.__doc__
|
return tell.__doc__
|
||||||
|
|
||||||
query = input.msg[6:].strip().partition(" ")
|
query = input.inp.partition(" ")
|
||||||
|
|
||||||
if query[0] == input.nick:
|
if query[0] == input.nick:
|
||||||
return "No."
|
return "No."
|
||||||
|
|
|
@ -31,7 +31,6 @@ def twitter(inp):
|
||||||
".twitter <user>/<user> <n>/<id>/#<hashtag>/@<user> -- gets last/<n>th tweet from"\
|
".twitter <user>/<user> <n>/<id>/#<hashtag>/@<user> -- gets last/<n>th tweet from"\
|
||||||
"<user>/gets tweet <id>/gets random tweet with #<hashtag>/gets replied tweet from @<user>"
|
"<user>/gets tweet <id>/gets random tweet with #<hashtag>/gets replied tweet from @<user>"
|
||||||
|
|
||||||
inp = inp.strip()
|
|
||||||
if not inp:
|
if not inp:
|
||||||
return twitter.__doc__
|
return twitter.__doc__
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,11 @@ from util import hook
|
||||||
@hook.command
|
@hook.command
|
||||||
def urban(inp):
|
def urban(inp):
|
||||||
'''.u/.urban <phrase> -- looks up <phrase> on urbandictionary.com'''
|
'''.u/.urban <phrase> -- looks up <phrase> on urbandictionary.com'''
|
||||||
if not inp.strip():
|
if not inp:
|
||||||
return urban.__doc__
|
return urban.__doc__
|
||||||
|
|
||||||
url = 'http://www.urbandictionary.com/define.php?term=' + \
|
url = 'http://www.urbandictionary.com/define.php?term=' + \
|
||||||
urllib.quote(inp.strip(), safe='')
|
urllib.quote(inp, safe='')
|
||||||
page = html.parse(url)
|
page = html.parse(url)
|
||||||
words = page.xpath("//td[@class='word']")
|
words = page.xpath("//td[@class='word']")
|
||||||
defs = page.xpath("//div[@class='definition']")
|
defs = page.xpath("//div[@class='definition']")
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import sqlite3
|
||||||
|
import pickle
|
||||||
|
import re
|
||||||
|
|
||||||
|
from util import hook, urlnorm
|
||||||
|
|
||||||
|
url_re = re.compile(r'([a-zA-Z]+://|www\.)[^ ]*')
|
||||||
|
|
||||||
|
dbname = "skybot.db"
|
||||||
|
|
||||||
|
expiration_period = 60 * 60 * 24 # 1 day
|
||||||
|
expiration_period_text = "24 hours"
|
||||||
|
|
||||||
|
ignored_urls = [urlnorm.normalize("http://google.com")]
|
||||||
|
|
||||||
|
def dbconnect(db):
|
||||||
|
"check to see that our db has the the seen table and return a connection."
|
||||||
|
conn = sqlite3.connect(db)
|
||||||
|
conn.execute("create table if not exists urlhistory"
|
||||||
|
"(server, chan, url, nick, time)")
|
||||||
|
conn.commit()
|
||||||
|
return conn
|
||||||
|
|
||||||
|
def insert_history(conn, server, chan, url, nick):
|
||||||
|
now = time.time()
|
||||||
|
conn.execute("insert into urlhistory(server, chan, url, nick, time) "
|
||||||
|
"values(?,?,?,?,?)", (server, chan, url, nick, time.time()))
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
def get_history(conn, server, chan, url):
|
||||||
|
conn.execute("delete from urlhistory where time < ?",
|
||||||
|
(time.time() - expiration_period,))
|
||||||
|
nicks = conn.execute("select nick from urlhistory where server=? "
|
||||||
|
"and chan=? and url=?", (server, chan, url)).fetchall()
|
||||||
|
return [x[0] for x in nicks]
|
||||||
|
|
||||||
|
def get_nicklist(nicks):
|
||||||
|
nicks = sorted(set(nicks), key=unicode.lower)
|
||||||
|
if len(nicks) <= 2:
|
||||||
|
return ' and '.join(nicks)
|
||||||
|
else:
|
||||||
|
return ', and '.join((', '.join(nicks[:-1]), nicks[-1]))
|
||||||
|
|
||||||
|
def ordinal(count):
|
||||||
|
return ["once", "twice", "%d times" % count][min(count, 3) - 1]
|
||||||
|
|
||||||
|
@hook.command(hook=r'(.*)', prefix=False)
|
||||||
|
def urlinput(bot, input):
|
||||||
|
dbpath = os.path.join(bot.persist_dir, dbname)
|
||||||
|
m = url_re.search(input.msg.encode('utf8'))
|
||||||
|
if not m:
|
||||||
|
return
|
||||||
|
|
||||||
|
# URL detected
|
||||||
|
conn = dbconnect(dbpath)
|
||||||
|
try:
|
||||||
|
url = urlnorm.normalize(m.group(0))
|
||||||
|
if url not in ignored_urls:
|
||||||
|
dupes = get_history(conn, input.server, input.chan, url)
|
||||||
|
insert_history(conn, input.server, input.chan, url, input.nick)
|
||||||
|
if dupes and input.nick not in dupes:
|
||||||
|
input.reply("That link has been posted " + ordinal(len(dupes))
|
||||||
|
+ " in the past " + expiration_period_text + " by " +
|
||||||
|
get_nicklist(dupes))
|
||||||
|
finally:
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
|
@ -0,0 +1,215 @@
|
||||||
|
"""
|
||||||
|
URI Normalization function:
|
||||||
|
* Always provide the URI scheme in lowercase characters.
|
||||||
|
* Always provide the host, if any, in lowercase characters.
|
||||||
|
* Only perform percent-encoding where it is essential.
|
||||||
|
* Always use uppercase A-through-F characters when percent-encoding.
|
||||||
|
* Prevent dot-segments appearing in non-relative URI paths.
|
||||||
|
* For schemes that define a default authority, use an empty authority if the
|
||||||
|
default is desired.
|
||||||
|
* For schemes that define an empty path to be equivalent to a path of "/",
|
||||||
|
use "/".
|
||||||
|
* For schemes that define a port, use an empty port if the default is desired
|
||||||
|
* All portions of the URI must be utf-8 encoded NFC from Unicode strings
|
||||||
|
|
||||||
|
implements:
|
||||||
|
http://gbiv.com/protocols/uri/rev-2002/rfc2396bis.html#canonical-form
|
||||||
|
http://www.intertwingly.net/wiki/pie/PaceCanonicalIds
|
||||||
|
|
||||||
|
inspired by:
|
||||||
|
Tony J. Ibbs, http://starship.python.net/crew/tibs/python/tji_url.py
|
||||||
|
Mark Nottingham, http://www.mnot.net/python/urlnorm.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
__license__ = "Python"
|
||||||
|
|
||||||
|
import re, unicodedata, urlparse
|
||||||
|
from urllib import quote, unquote
|
||||||
|
|
||||||
|
default_port = {
|
||||||
|
'ftp': 21,
|
||||||
|
'telnet': 23,
|
||||||
|
'http': 80,
|
||||||
|
'gopher': 70,
|
||||||
|
'news': 119,
|
||||||
|
'nntp': 119,
|
||||||
|
'prospero': 191,
|
||||||
|
'https': 443,
|
||||||
|
'snews': 563,
|
||||||
|
'snntp': 563,
|
||||||
|
}
|
||||||
|
|
||||||
|
def normalize(url):
|
||||||
|
"""Normalize a URL."""
|
||||||
|
|
||||||
|
scheme,auth,path,query,fragment = urlparse.urlsplit(url.strip())
|
||||||
|
(userinfo,host,port)=re.search('([^@]*@)?([^:]*):?(.*)',auth).groups()
|
||||||
|
|
||||||
|
# Always provide the URI scheme in lowercase characters.
|
||||||
|
scheme = scheme.lower()
|
||||||
|
|
||||||
|
# Always provide the host, if any, in lowercase characters.
|
||||||
|
host = host.lower()
|
||||||
|
if host and host[-1] == '.': host = host[:-1]
|
||||||
|
if host and host.startswith("www."):
|
||||||
|
if not scheme: scheme = "http"
|
||||||
|
host = host[4:]
|
||||||
|
elif path and path.startswith("www."):
|
||||||
|
if not scheme: scheme = "http"
|
||||||
|
path = path[4:]
|
||||||
|
|
||||||
|
# Only perform percent-encoding where it is essential.
|
||||||
|
# Always use uppercase A-through-F characters when percent-encoding.
|
||||||
|
# All portions of the URI must be utf-8 encoded NFC from Unicode strings
|
||||||
|
def clean(string):
|
||||||
|
string=unicode(unquote(string),'utf-8','replace')
|
||||||
|
return unicodedata.normalize('NFC',string).encode('utf-8')
|
||||||
|
path=quote(clean(path),"~:/?#[]@!$&'()*+,;=")
|
||||||
|
fragment=quote(clean(fragment),"~")
|
||||||
|
|
||||||
|
# note care must be taken to only encode & and = characters as values
|
||||||
|
query="&".join(["=".join([quote(clean(t) ,"~:/?#[]@!$'()*+,;=")
|
||||||
|
for t in q.split("=",1)]) for q in query.split("&")])
|
||||||
|
|
||||||
|
# Prevent dot-segments appearing in non-relative URI paths.
|
||||||
|
if scheme in ["","http","https","ftp","file"]:
|
||||||
|
output=[]
|
||||||
|
for input in path.split('/'):
|
||||||
|
if input=="":
|
||||||
|
if not output: output.append(input)
|
||||||
|
elif input==".":
|
||||||
|
pass
|
||||||
|
elif input=="..":
|
||||||
|
if len(output)>1: output.pop()
|
||||||
|
else:
|
||||||
|
output.append(input)
|
||||||
|
if input in ["",".",".."]: output.append("")
|
||||||
|
path='/'.join(output)
|
||||||
|
|
||||||
|
# For schemes that define a default authority, use an empty authority if
|
||||||
|
# the default is desired.
|
||||||
|
if userinfo in ["@",":@"]: userinfo=""
|
||||||
|
|
||||||
|
# For schemes that define an empty path to be equivalent to a path of "/",
|
||||||
|
# use "/".
|
||||||
|
if path=="" and scheme in ["http","https","ftp","file"]:
|
||||||
|
path="/"
|
||||||
|
|
||||||
|
# For schemes that define a port, use an empty port if the default is
|
||||||
|
# desired
|
||||||
|
if port and scheme in default_port.keys():
|
||||||
|
if port.isdigit():
|
||||||
|
port=str(int(port))
|
||||||
|
if int(port)==default_port[scheme]:
|
||||||
|
port = ''
|
||||||
|
|
||||||
|
# Put it all back together again
|
||||||
|
auth=(userinfo or "") + host
|
||||||
|
if port: auth+=":"+port
|
||||||
|
if url.endswith("#") and query=="" and fragment=="": path+="#"
|
||||||
|
return urlparse.urlunsplit((scheme,auth,path,query,fragment)).replace("http:///", "http://")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import unittest
|
||||||
|
suite = unittest.TestSuite()
|
||||||
|
|
||||||
|
""" from http://www.intertwingly.net/wiki/pie/PaceCanonicalIds """
|
||||||
|
tests= [
|
||||||
|
(False, "http://:@example.com/"),
|
||||||
|
(False, "http://@example.com/"),
|
||||||
|
(False, "http://example.com"),
|
||||||
|
(False, "HTTP://example.com/"),
|
||||||
|
(False, "http://EXAMPLE.COM/"),
|
||||||
|
(False, "http://example.com/%7Ejane"),
|
||||||
|
(False, "http://example.com/?q=%C7"),
|
||||||
|
(False, "http://example.com/?q=%5c"),
|
||||||
|
(False, "http://example.com/?q=C%CC%A7"),
|
||||||
|
(False, "http://example.com/a/../a/b"),
|
||||||
|
(False, "http://example.com/a/./b"),
|
||||||
|
(False, "http://example.com:80/"),
|
||||||
|
(True, "http://example.com/"),
|
||||||
|
(True, "http://example.com/?q=%C3%87"),
|
||||||
|
(True, "http://example.com/?q=%E2%85%A0"),
|
||||||
|
(True, "http://example.com/?q=%5C"),
|
||||||
|
(True, "http://example.com/~jane"),
|
||||||
|
(True, "http://example.com/a/b"),
|
||||||
|
(True, "http://example.com:8080/"),
|
||||||
|
(True, "http://user:password@example.com/"),
|
||||||
|
|
||||||
|
# from rfc2396bis
|
||||||
|
(True, "ftp://ftp.is.co.za/rfc/rfc1808.txt"),
|
||||||
|
(True, "http://www.ietf.org/rfc/rfc2396.txt"),
|
||||||
|
(True, "ldap://[2001:db8::7]/c=GB?objectClass?one"),
|
||||||
|
(True, "mailto:John.Doe@example.com"),
|
||||||
|
(True, "news:comp.infosystems.www.servers.unix"),
|
||||||
|
(True, "tel:+1-816-555-1212"),
|
||||||
|
(True, "telnet://192.0.2.16:80/"),
|
||||||
|
(True, "urn:oasis:names:specification:docbook:dtd:xml:4.1.2"),
|
||||||
|
|
||||||
|
# other
|
||||||
|
(True, "http://127.0.0.1/"),
|
||||||
|
(False, "http://127.0.0.1:80/"),
|
||||||
|
(True, "http://www.w3.org/2000/01/rdf-schema#"),
|
||||||
|
(False, "http://example.com:081/"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def testcase(expected,value):
|
||||||
|
class test(unittest.TestCase):
|
||||||
|
def runTest(self):
|
||||||
|
assert (normalize(value)==value)==expected, \
|
||||||
|
(expected, value, normalize(value))
|
||||||
|
return test()
|
||||||
|
|
||||||
|
for (expected,value) in tests:
|
||||||
|
suite.addTest(testcase(expected,value))
|
||||||
|
|
||||||
|
""" mnot test suite; three tests updated for rfc2396bis. """
|
||||||
|
tests = {
|
||||||
|
'/foo/bar/.': '/foo/bar/',
|
||||||
|
'/foo/bar/./': '/foo/bar/',
|
||||||
|
'/foo/bar/..': '/foo/',
|
||||||
|
'/foo/bar/../': '/foo/',
|
||||||
|
'/foo/bar/../baz': '/foo/baz',
|
||||||
|
'/foo/bar/../..': '/',
|
||||||
|
'/foo/bar/../../': '/',
|
||||||
|
'/foo/bar/../../baz': '/baz',
|
||||||
|
'/foo/bar/../../../baz': '/baz', #was: '/../baz',
|
||||||
|
'/foo/bar/../../../../baz': '/baz',
|
||||||
|
'/./foo': '/foo',
|
||||||
|
'/../foo': '/foo', #was: '/../foo',
|
||||||
|
'/foo.': '/foo.',
|
||||||
|
'/.foo': '/.foo',
|
||||||
|
'/foo..': '/foo..',
|
||||||
|
'/..foo': '/..foo',
|
||||||
|
'/./../foo': '/foo', #was: '/../foo',
|
||||||
|
'/./foo/.': '/foo/',
|
||||||
|
'/foo/./bar': '/foo/bar',
|
||||||
|
'/foo/../bar': '/bar',
|
||||||
|
'/foo//': '/foo/',
|
||||||
|
'/foo///bar//': '/foo/bar/',
|
||||||
|
'http://www.foo.com:80/foo': 'http://www.foo.com/foo',
|
||||||
|
'http://www.foo.com:8000/foo': 'http://www.foo.com:8000/foo',
|
||||||
|
'http://www.foo.com./foo/bar.html': 'http://www.foo.com/foo/bar.html',
|
||||||
|
'http://www.foo.com.:81/foo': 'http://www.foo.com:81/foo',
|
||||||
|
'http://www.foo.com/%7ebar': 'http://www.foo.com/~bar',
|
||||||
|
'http://www.foo.com/%7Ebar': 'http://www.foo.com/~bar',
|
||||||
|
'ftp://user:pass@ftp.foo.net/foo/bar':
|
||||||
|
'ftp://user:pass@ftp.foo.net/foo/bar',
|
||||||
|
'http://USER:pass@www.Example.COM/foo/bar':
|
||||||
|
'http://USER:pass@www.example.com/foo/bar',
|
||||||
|
'http://www.example.com./': 'http://www.example.com/',
|
||||||
|
'-': '-',
|
||||||
|
}
|
||||||
|
|
||||||
|
def testcase(original,normalized):
|
||||||
|
class test(unittest.TestCase):
|
||||||
|
def runTest(self):
|
||||||
|
assert normalize(original)==normalized, \
|
||||||
|
(original, normalized, normalize(original))
|
||||||
|
return test()
|
||||||
|
|
||||||
|
for (original,normalized) in tests.items():
|
||||||
|
suite.addTest(testcase(original,normalized))
|
||||||
|
|
||||||
|
""" execute tests """
|
||||||
|
unittest.TextTestRunner().run(suite)
|
|
@ -41,7 +41,7 @@ def weather(bot, input):
|
||||||
stalk = load_stalk(filename)
|
stalk = load_stalk(filename)
|
||||||
|
|
||||||
nick = input.nick.lower()
|
nick = input.nick.lower()
|
||||||
loc = input.inp.strip()
|
loc = input.inp
|
||||||
dontsave = loc.endswith(" dontsave")
|
dontsave = loc.endswith(" dontsave")
|
||||||
if dontsave:
|
if dontsave:
|
||||||
loc = loc[:-9].strip().lower()
|
loc = loc[:-9].strip().lower()
|
||||||
|
|
|
@ -14,16 +14,16 @@ search_url = api_prefix + "?action=opensearch&search=%s&format=xml"
|
||||||
paren_re = re.compile('\s*\(.*\)$')
|
paren_re = re.compile('\s*\(.*\)$')
|
||||||
|
|
||||||
|
|
||||||
@hook.command(hook='w(\s+.*|$)')
|
@hook.command('w')
|
||||||
@hook.command
|
@hook.command
|
||||||
def wiki(query):
|
def wiki(inp):
|
||||||
'''.w/.wiki <phrase> -- gets first sentence of wikipedia ''' \
|
'''.w/.wiki <phrase> -- gets first sentence of wikipedia ''' \
|
||||||
'''article on <phrase>'''
|
'''article on <phrase>'''
|
||||||
|
|
||||||
if not query.strip():
|
if not inp:
|
||||||
return wiki.__doc__
|
return wiki.__doc__
|
||||||
|
|
||||||
q = search_url % (urllib.quote(query.strip(), safe=''))
|
q = search_url % (urllib.quote(inp, safe=''))
|
||||||
x = etree.parse(q)
|
x = etree.parse(q)
|
||||||
|
|
||||||
ns = '{http://opensearch.org/searchsuggest2}'
|
ns = '{http://opensearch.org/searchsuggest2}'
|
||||||
|
|
Loading…
Reference in New Issue