initial commit
This commit is contained in:
commit
33f801bc5e
|
@ -0,0 +1,12 @@
|
||||||
|
.*.swp
|
||||||
|
*.pyc
|
||||||
|
*.orig
|
||||||
|
persist
|
||||||
|
config
|
||||||
|
pep8.py
|
||||||
|
.project
|
||||||
|
.pydevproject
|
||||||
|
*.db
|
||||||
|
web
|
||||||
|
env
|
||||||
|
config.yml
|
|
@ -0,0 +1,7 @@
|
||||||
|
Copyright 2015 Christine Dodrill <xena@yolo-swag.com>
|
||||||
|
|
||||||
|
Usage of the works is permitted provided that this instrument is retained
|
||||||
|
with the works, so that any entity that uses the works is notified of
|
||||||
|
this instrument.
|
||||||
|
|
||||||
|
DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
|
|
@ -0,0 +1,35 @@
|
||||||
|
h2
|
||||||
|
==
|
||||||
|
|
||||||
|
Or
|
||||||
|
|
||||||
|
H: revenge of the trolls
|
||||||
|
|
||||||
|
The matrix version of the shitposting utility bot you won't be able to live without.
|
||||||
|
|
||||||
|
Setup
|
||||||
|
-----
|
||||||
|
|
||||||
|
- Install the virtualenv and all of the deps with the handy `env.sh` script.
|
||||||
|
- Create a https://matrix.org account and add it to the config:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
me:
|
||||||
|
user: >-
|
||||||
|
@h:matrix.org
|
||||||
|
password: OMITTED HERE YOU HACKERS
|
||||||
|
homeserver: https://matrix.org
|
||||||
|
|
||||||
|
masters:
|
||||||
|
- >-
|
||||||
|
@Xena:matrix.org
|
||||||
|
```
|
||||||
|
|
||||||
|
- Save this as config.yml
|
||||||
|
- Kick off `main.py`
|
||||||
|
|
||||||
|
Notes / Known Bugs
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- This must already be joined to channels and cannot join them
|
||||||
|
- This is really janky and could fall over at any moment
|
|
@ -0,0 +1,14 @@
|
||||||
|
import os
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
|
||||||
|
def get_db_connection(conn, name=''):
|
||||||
|
"returns an sqlite3 connection to a persistent database"
|
||||||
|
|
||||||
|
if not name:
|
||||||
|
name = '%s.%s.db' % (conn.nick, conn.server)
|
||||||
|
|
||||||
|
filename = os.path.join(bot.persist_dir, name)
|
||||||
|
return sqlite3.connect(filename, timeout=10)
|
||||||
|
|
||||||
|
bot.get_db_connection = get_db_connection
|
|
@ -0,0 +1,199 @@
|
||||||
|
import fuckit
|
||||||
|
import Queue
|
||||||
|
import thread
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
thread.stack_size(1024 * 512) # reduce vm size
|
||||||
|
|
||||||
|
class Input(dict):
|
||||||
|
def __init__(self, conn, raw, prefix, command, params,
|
||||||
|
nick, user, host, paraml, msg):
|
||||||
|
chan = paraml[0].lower()
|
||||||
|
if chan == conn.nick.lower(): # is a PM
|
||||||
|
chan = nick
|
||||||
|
|
||||||
|
def say(msg):
|
||||||
|
conn.msg(chan, msg)
|
||||||
|
|
||||||
|
def reply(msg):
|
||||||
|
if chan == nick: # PMs don't need prefixes
|
||||||
|
self.say(msg)
|
||||||
|
else:
|
||||||
|
self.say('> ' + msg)
|
||||||
|
|
||||||
|
def pm(msg, nick=nick):
|
||||||
|
conn.msg(nick, msg)
|
||||||
|
|
||||||
|
def set_nick(nick):
|
||||||
|
conn.set_nick(nick)
|
||||||
|
|
||||||
|
def me(msg):
|
||||||
|
self.say("\x01%s %s\x01" % ("ACTION", msg))
|
||||||
|
|
||||||
|
def notice(msg):
|
||||||
|
conn.cmd('NOTICE', [nick, msg])
|
||||||
|
|
||||||
|
def kick(target=None, reason=None):
|
||||||
|
conn.cmd('KICK', [chan, target or nick, reason or ''])
|
||||||
|
|
||||||
|
def ban(target=None):
|
||||||
|
conn.cmd('MODE', [chan, '+b', target or host])
|
||||||
|
|
||||||
|
def unban(target=None):
|
||||||
|
conn.cmd('MODE', [chan, '-b', target or host])
|
||||||
|
|
||||||
|
|
||||||
|
dict.__init__(self, conn=conn, raw=raw, prefix=prefix, command=command,
|
||||||
|
params=params, nick=nick, user=user, host=host,
|
||||||
|
paraml=paraml, msg=msg, server=conn.server, chan=chan,
|
||||||
|
notice=notice, say=say, reply=reply, pm=pm, bot=bot,
|
||||||
|
kick=kick, ban=ban, unban=unban, me=me,
|
||||||
|
set_nick=set_nick, lastparam=paraml[-1])
|
||||||
|
|
||||||
|
# make dict keys accessible as attributes
|
||||||
|
def __getattr__(self, key):
|
||||||
|
return self[key]
|
||||||
|
|
||||||
|
def __setattr__(self, key, value):
|
||||||
|
self[key] = value
|
||||||
|
|
||||||
|
#@fuckit
|
||||||
|
def run(func, input):
|
||||||
|
args = func._args
|
||||||
|
|
||||||
|
if 'inp' not in input:
|
||||||
|
input.inp = input.paraml
|
||||||
|
|
||||||
|
if args:
|
||||||
|
if 'db' in args and 'db' not in input:
|
||||||
|
input.db = get_db_connection(input.conn)
|
||||||
|
if 'input' in args:
|
||||||
|
input.input = input
|
||||||
|
if 0 in args:
|
||||||
|
out = func(input.inp, **input)
|
||||||
|
else:
|
||||||
|
kw = dict((key, input[key]) for key in args if key in input)
|
||||||
|
out = func(input.inp, **kw)
|
||||||
|
else:
|
||||||
|
out = func(input.inp)
|
||||||
|
if out is not None:
|
||||||
|
input.reply(unicode(out))
|
||||||
|
|
||||||
|
|
||||||
|
class Handler(object):
|
||||||
|
'''Runs plugins in their own threads (ensures order)'''
|
||||||
|
def __init__(self, func):
|
||||||
|
self.func = func
|
||||||
|
self.input_queue = Queue.Queue()
|
||||||
|
thread.start_new_thread(self.start, ())
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
uses_db = 'db' in self.func._args
|
||||||
|
db_conns = {}
|
||||||
|
while True:
|
||||||
|
input = self.input_queue.get()
|
||||||
|
|
||||||
|
if input == StopIteration:
|
||||||
|
break
|
||||||
|
|
||||||
|
if uses_db:
|
||||||
|
db = db_conns.get(input.conn)
|
||||||
|
if db is None:
|
||||||
|
db = bot.get_db_connection(input.conn)
|
||||||
|
db_conns[input.conn] = db
|
||||||
|
input.db = db
|
||||||
|
|
||||||
|
try:
|
||||||
|
run(self.func, input)
|
||||||
|
except:
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.input_queue.put(StopIteration)
|
||||||
|
|
||||||
|
def put(self, value):
|
||||||
|
self.input_queue.put(value)
|
||||||
|
|
||||||
|
|
||||||
|
def dispatch(input, kind, func, args, autohelp=False):
|
||||||
|
for sieve, in bot.plugs['sieve']:
|
||||||
|
input = do_sieve(sieve, bot, input, func, kind, args)
|
||||||
|
if input == None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if autohelp and args.get('autohelp', True) and not input.inp \
|
||||||
|
and func.__doc__ is not None:
|
||||||
|
input.reply(func.__doc__)
|
||||||
|
return
|
||||||
|
|
||||||
|
if hasattr(func, '_apikey'):
|
||||||
|
key = bot.config.get('api_keys', {}).get(func._apikey, None)
|
||||||
|
if key is None:
|
||||||
|
input.reply('error: missing api key')
|
||||||
|
return
|
||||||
|
input.api_key = key
|
||||||
|
|
||||||
|
if func._thread:
|
||||||
|
bot.threads[func].put(input)
|
||||||
|
else:
|
||||||
|
thread.start_new_thread(run, (func, input))
|
||||||
|
|
||||||
|
|
||||||
|
def match_command(command):
|
||||||
|
commands = list(bot.commands)
|
||||||
|
|
||||||
|
# do some fuzzy matching
|
||||||
|
prefix = filter(lambda x: x.startswith(command), commands)
|
||||||
|
if len(prefix) == 1:
|
||||||
|
return prefix[0]
|
||||||
|
elif prefix and command not in prefix:
|
||||||
|
return prefix
|
||||||
|
|
||||||
|
return command
|
||||||
|
|
||||||
|
|
||||||
|
def main(conn, out):
|
||||||
|
inp = Input(conn, *out)
|
||||||
|
|
||||||
|
# EVENTS
|
||||||
|
for func, args in bot.events[inp.command] + bot.events['*']:
|
||||||
|
dispatch(Input(conn, *out), "event", func, args)
|
||||||
|
|
||||||
|
if inp.command == 'PRIVMSG':
|
||||||
|
# COMMANDS
|
||||||
|
bot_prefix = re.escape(bot.config.get("prefix", "."))
|
||||||
|
if inp.chan == inp.nick: # private message, no command prefix required
|
||||||
|
prefix = r'^(?:(?:'+bot_prefix+')?|'
|
||||||
|
else:
|
||||||
|
prefix = r'^(?:'+bot_prefix+'|'
|
||||||
|
|
||||||
|
command_re = prefix + inp.conn.nick
|
||||||
|
command_re += r'[:,]+\s+)(\w+)(?:$|\s+)(.*)'
|
||||||
|
|
||||||
|
m = re.match(command_re, inp.lastparam)
|
||||||
|
|
||||||
|
if m:
|
||||||
|
trigger = m.group(1).lower()
|
||||||
|
command = match_command(trigger)
|
||||||
|
|
||||||
|
if isinstance(command, list): # multiple potential matches
|
||||||
|
input = Input(conn, *out)
|
||||||
|
input.reply("did you mean %s or %s?" %
|
||||||
|
(', '.join(command[:-1]), command[-1]))
|
||||||
|
elif command in bot.commands:
|
||||||
|
input = Input(conn, *out)
|
||||||
|
input.trigger = trigger
|
||||||
|
input.inp_unstripped = m.group(2)
|
||||||
|
input.inp = input.inp_unstripped.strip()
|
||||||
|
|
||||||
|
func, args = bot.commands[command]
|
||||||
|
dispatch(input, "command", func, args, autohelp=True)
|
||||||
|
|
||||||
|
# REGEXES
|
||||||
|
for func, args in bot.plugs['regex']:
|
||||||
|
m = args['re'].search(inp.lastparam)
|
||||||
|
if m:
|
||||||
|
input = Input(conn, *out)
|
||||||
|
input.inp = m
|
||||||
|
|
||||||
|
dispatch(input, "regex", func, args)
|
|
@ -0,0 +1,161 @@
|
||||||
|
import collections
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
|
||||||
|
if 'mtimes' not in globals():
|
||||||
|
mtimes = {}
|
||||||
|
|
||||||
|
if 'lastfiles' not in globals():
|
||||||
|
lastfiles = set()
|
||||||
|
|
||||||
|
|
||||||
|
def make_signature(f):
|
||||||
|
return f.func_code.co_filename, f.func_name, f.func_code.co_firstlineno
|
||||||
|
|
||||||
|
|
||||||
|
def format_plug(plug, kind='', lpad=0, width=40):
|
||||||
|
out = ' ' * lpad + '%s:%s:%s' % make_signature(plug[0])
|
||||||
|
if kind == 'command':
|
||||||
|
out += ' ' * (50 - len(out)) + plug[1]['name']
|
||||||
|
|
||||||
|
if kind == 'event':
|
||||||
|
out += ' ' * (50 - len(out)) + ', '.join(plug[1]['events'])
|
||||||
|
|
||||||
|
if kind == 'regex':
|
||||||
|
out += ' ' * (50 - len(out)) + plug[1]['regex']
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def reload(init=False):
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
if init:
|
||||||
|
bot.plugs = collections.defaultdict(list)
|
||||||
|
bot.threads = {}
|
||||||
|
|
||||||
|
core_fileset = set(glob.glob(os.path.join("core", "*.py")))
|
||||||
|
|
||||||
|
for filename in core_fileset:
|
||||||
|
mtime = os.stat(filename).st_mtime
|
||||||
|
if mtime != mtimes.get(filename):
|
||||||
|
mtimes[filename] = mtime
|
||||||
|
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
eval(compile(open(filename, 'U').read(), filename, 'exec'),
|
||||||
|
globals())
|
||||||
|
except Exception:
|
||||||
|
traceback.print_exc()
|
||||||
|
if init: # stop if there's an error (syntax?) in a core
|
||||||
|
sys.exit() # script on startup
|
||||||
|
continue
|
||||||
|
|
||||||
|
if filename == os.path.join('core', 'reload.py'):
|
||||||
|
reload(init=init)
|
||||||
|
return
|
||||||
|
|
||||||
|
fileset = set(glob.glob(os.path.join('plugins', '*.py')))
|
||||||
|
|
||||||
|
# remove deleted/moved plugins
|
||||||
|
for name, data in bot.plugs.iteritems():
|
||||||
|
bot.plugs[name] = [x for x in data if x[0]._filename in fileset]
|
||||||
|
|
||||||
|
for filename in list(mtimes):
|
||||||
|
if filename not in fileset and filename not in core_fileset:
|
||||||
|
mtimes.pop(filename)
|
||||||
|
|
||||||
|
for func, handler in list(bot.threads.iteritems()):
|
||||||
|
if func._filename not in fileset:
|
||||||
|
handler.stop()
|
||||||
|
del bot.threads[func]
|
||||||
|
|
||||||
|
# compile new plugins
|
||||||
|
for filename in fileset:
|
||||||
|
mtime = os.stat(filename).st_mtime
|
||||||
|
if mtime != mtimes.get(filename):
|
||||||
|
mtimes[filename] = mtime
|
||||||
|
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
code = compile(open(filename, 'U').read(), filename, 'exec')
|
||||||
|
namespace = {}
|
||||||
|
eval(code, namespace)
|
||||||
|
except Exception:
|
||||||
|
traceback.print_exc()
|
||||||
|
continue
|
||||||
|
|
||||||
|
# remove plugins already loaded from this filename
|
||||||
|
for name, data in bot.plugs.iteritems():
|
||||||
|
bot.plugs[name] = [x for x in data
|
||||||
|
if x[0]._filename != filename]
|
||||||
|
|
||||||
|
for func, handler in list(bot.threads.iteritems()):
|
||||||
|
if func._filename == filename:
|
||||||
|
handler.stop()
|
||||||
|
del bot.threads[func]
|
||||||
|
|
||||||
|
for obj in namespace.itervalues():
|
||||||
|
if hasattr(obj, '_hook'): # check for magic
|
||||||
|
if obj._thread:
|
||||||
|
bot.threads[obj] = Handler(obj)
|
||||||
|
|
||||||
|
for type, data in obj._hook:
|
||||||
|
bot.plugs[type] += [data]
|
||||||
|
|
||||||
|
if not init:
|
||||||
|
print '### new plugin (type: %s) loaded:' % \
|
||||||
|
type, format_plug(data)
|
||||||
|
|
||||||
|
if changed:
|
||||||
|
bot.commands = {}
|
||||||
|
for plug in bot.plugs['command']:
|
||||||
|
name = plug[1]['name'].lower()
|
||||||
|
if not re.match(r'^\w+$', name):
|
||||||
|
print '### ERROR: invalid command name "%s" (%s)' % (name,
|
||||||
|
format_plug(plug))
|
||||||
|
continue
|
||||||
|
if name in bot.commands:
|
||||||
|
print "### ERROR: command '%s' already registered (%s, %s)" % \
|
||||||
|
(name, format_plug(bot.commands[name]),
|
||||||
|
format_plug(plug))
|
||||||
|
continue
|
||||||
|
bot.commands[name] = plug
|
||||||
|
|
||||||
|
bot.events = collections.defaultdict(list)
|
||||||
|
for func, args in bot.plugs['event']:
|
||||||
|
for event in args['events']:
|
||||||
|
bot.events[event].append((func, args))
|
||||||
|
|
||||||
|
if init:
|
||||||
|
print ' plugin listing:'
|
||||||
|
|
||||||
|
if bot.commands:
|
||||||
|
# hack to make commands with multiple aliases
|
||||||
|
# print nicely
|
||||||
|
|
||||||
|
print ' command:'
|
||||||
|
commands = collections.defaultdict(list)
|
||||||
|
|
||||||
|
for name, (func, args) in bot.commands.iteritems():
|
||||||
|
commands[make_signature(func)].append(name)
|
||||||
|
|
||||||
|
for sig, names in sorted(commands.iteritems()):
|
||||||
|
names.sort(key=lambda x: (-len(x), x)) # long names first
|
||||||
|
out = ' ' * 6 + '%s:%s:%s' % sig
|
||||||
|
out += ' ' * (50 - len(out)) + ', '.join(names)
|
||||||
|
print out
|
||||||
|
|
||||||
|
for kind, plugs in sorted(bot.plugs.iteritems()):
|
||||||
|
if kind == 'command':
|
||||||
|
continue
|
||||||
|
print ' %s:' % kind
|
||||||
|
for plug in plugs:
|
||||||
|
print format_plug(plug, kind=kind, lpad=6)
|
||||||
|
print
|
|
@ -0,0 +1,7 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
virtualenv env
|
||||||
|
. ./env/bin/activate
|
||||||
|
|
||||||
|
pip install git+https://github.com/billpmurphy/hask
|
||||||
|
pip install -r requirements.txt
|
|
@ -0,0 +1,67 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
import time
|
||||||
|
|
||||||
|
from matrix_client.client import MatrixClient
|
||||||
|
from time import sleep
|
||||||
|
from yaml import load, dump
|
||||||
|
|
||||||
|
class Bot(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.conns = {}
|
||||||
|
self.persist_dir = os.path.abspath('persist')
|
||||||
|
if not os.path.exists(self.persist_dir):
|
||||||
|
os.mkdir(self.persist_dir)
|
||||||
|
|
||||||
|
bot = Bot()
|
||||||
|
|
||||||
|
sys.path += ['plugins']
|
||||||
|
|
||||||
|
# bootstrap the reloader
|
||||||
|
eval(compile(open(os.path.join('core', 'reload.py'), 'U').read(),
|
||||||
|
os.path.join('core', 'reload.py'), 'exec'), globals())
|
||||||
|
reload(init=True)
|
||||||
|
|
||||||
|
print "matrix has u"
|
||||||
|
|
||||||
|
config = {}
|
||||||
|
|
||||||
|
with open("./config.yml", "r") as fin:
|
||||||
|
config = load(fin.read())
|
||||||
|
|
||||||
|
client = MatrixClient(config["me"]["homeserver"])
|
||||||
|
token = client.login_with_password(username=config["me"]["user"], password=config["me"]["password"])
|
||||||
|
|
||||||
|
rooms = client.get_rooms()
|
||||||
|
|
||||||
|
def room_callback(event):
|
||||||
|
room = rooms[event[u'room_id']]
|
||||||
|
if u'user_id' in event and event[u'type'] == "m.room.message":
|
||||||
|
print room.name, "<"+event[u'user_id']+">", event[u'content'][u'body']
|
||||||
|
if event[u'user_id'] == config["me"]["user"]:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
content = event[u'content']
|
||||||
|
body = content[u'body']
|
||||||
|
|
||||||
|
if body.startswith("."):
|
||||||
|
body = body.replace(".", "", 1)
|
||||||
|
splitstuff = body.split()
|
||||||
|
command = splitstuff[0]
|
||||||
|
args = " ".join(splitstuff[1:])
|
||||||
|
cmd = match_command(command)
|
||||||
|
if cmd in bot.commands:
|
||||||
|
room.send_text(bot.commands[cmd][0](args))
|
||||||
|
else:
|
||||||
|
for func, args in bot.plugs["regex"]:
|
||||||
|
m = args['re'].search(body)
|
||||||
|
if m:
|
||||||
|
room.send_text(func(m))
|
||||||
|
|
||||||
|
for room in rooms:
|
||||||
|
rooms[room].add_listener(room_callback)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
client.listen_for_events()
|
||||||
|
sleep(0.25)
|
|
@ -0,0 +1,15 @@
|
||||||
|
from util import hook
|
||||||
|
from random import choice
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
quotes = []
|
||||||
|
|
||||||
|
with open("./plugins/data/bobross.json", "r") as fin:
|
||||||
|
print fin
|
||||||
|
quotes = json.load(fin)
|
||||||
|
|
||||||
|
@hook.regex("^[Bb]ob [Rr]oss fact$")
|
||||||
|
@hook.command
|
||||||
|
def bobross(inp):
|
||||||
|
return choice(quotes)
|
|
@ -0,0 +1,9 @@
|
||||||
|
import re
|
||||||
|
import requests
|
||||||
|
from util import hook
|
||||||
|
|
||||||
|
@hook.command
|
||||||
|
@hook.regex("^feel good fact$", re.IGNORECASE)
|
||||||
|
def compliment(inp):
|
||||||
|
r = requests.get("http://compliment.b303.me/")
|
||||||
|
return r.text
|
|
@ -0,0 +1,166 @@
|
||||||
|
[
|
||||||
|
"That's a crooked tree. We'll send him to Washington.",
|
||||||
|
"The only thing worse than yellow snow is green snow.",
|
||||||
|
"I like to beat the brush.",
|
||||||
|
"In painting, you have unlimited power. You have the ability to move mountains. You can bend rivers. But when I get home, the only thing I have power over is the garbage.",
|
||||||
|
"You need the dark in order to show the light.",
|
||||||
|
"Look around. Look at what we have. Beauty is everywhere you only have to look to see it.",
|
||||||
|
"Just go out and talk to a tree. Make friends with it.",
|
||||||
|
"There's nothing wrong with having a tree as a friend.",
|
||||||
|
"Trees cover up a multitude of sins.",
|
||||||
|
"They say everything looks better with odd numbers of things. But sometimes I put even numbers\u00e2\u20ac\u201djust to upset the critics.",
|
||||||
|
"How do you make a round circle with a square knife? That's your challenge for the day.",
|
||||||
|
"A thin paint will stick to a thick paint.",
|
||||||
|
"Just beat the devil out of it.",
|
||||||
|
"We don't make mistakes we just have happy little accidents.",
|
||||||
|
"We'll paint one happy little tree right here.",
|
||||||
|
"Just pretend you are a whisper floating across a mountain.",
|
||||||
|
"From all of us here, I want to wish you happy painting and God bless, my friends.",
|
||||||
|
"We tell people sometimes: we're like drug dealers, come into town and get everybody absolutely addicted to painting. It doesn't take much to get you addicted.",
|
||||||
|
"The secret to doing anything is believing that you can do it. Anything that you believe you can do strong enough, you can do. Anything. As long as you believe.",
|
||||||
|
"Water's like me. It's laaazy\u00c2 ... Boy, it always looks for the easiest way to do things",
|
||||||
|
"I really believe that if you practice enough you could paint the 'Mona Lisa' with a two-inch brush.",
|
||||||
|
"If I paint something, I don't want to have to explain what it is.",
|
||||||
|
"We artists are a different breed of people. We're a happy bunch.",
|
||||||
|
"I guess I'm a little weird. I like to talk to trees and animals. That's okay though; I have more fun than most people.",
|
||||||
|
"Let's get crazy.",
|
||||||
|
"I can't think of anything more rewarding than being able to express yourself to others through painting.",
|
||||||
|
"Exercising the imagination, experimenting with talents, being creative; these things, to me, are truly the windows to your soul.",
|
||||||
|
"All you need to paint is a few tools, a little instruction, and a vision in your mind.",
|
||||||
|
"I started painting as a hobby when I was little. I didn't know I had any talent. I believe talent is just a pursued interest. Anybody can do what I do.",
|
||||||
|
"Everyone is going to see things differently - and that's the way it should be.",
|
||||||
|
"No pressure. Just relax and watch it happen.",
|
||||||
|
"When you do it your way you can go anywhere you choose.",
|
||||||
|
"You can create beautiful things - but you have to see them in your mind first",
|
||||||
|
"It's so important to do something every day that will make you happy",
|
||||||
|
"Don't be afraid to make these big decisions. Once you start, they sort of just make themselves.",
|
||||||
|
"With something so strong, a little bit can go a long way.",
|
||||||
|
"You have to allow the paint to break to make it beautiful.",
|
||||||
|
"Think about a cloud. Just float around and be there.",
|
||||||
|
"In nature, dead trees are just as normal as live trees.",
|
||||||
|
"It's hard to see things when you're too close. Take a step back and look.",
|
||||||
|
"Only think about one thing at a time. Don't get greedy.",
|
||||||
|
"If you do too much it's going to lose its effectiveness.",
|
||||||
|
"Use absolutely no pressure. Just like an angel's wing.",
|
||||||
|
"You're meant to have fun in life.",
|
||||||
|
"In life you need colors.",
|
||||||
|
"It's a super day, so why not make a beautiful sky?",
|
||||||
|
"Just let go - and fall like a little waterfall.",
|
||||||
|
"It's beautiful - and we haven't even done anything to it yet.",
|
||||||
|
"Be careful. You can always add more - but you can't take it away.",
|
||||||
|
"Pretend you're water. Just floating without any effort. Having a good day.",
|
||||||
|
"When things happen - enjoy them. They're little gifts.",
|
||||||
|
"Take your time. Speed will come later.",
|
||||||
|
"It's amazing what you can do with a little love in your heart.",
|
||||||
|
"God gave you this gift of imagination. Use it.",
|
||||||
|
"Paint anything you want on the canvas. Create your own world.",
|
||||||
|
"That's what painting is all about. It should make you feel good when you paint.",
|
||||||
|
"You can do anything your heart can imagine.",
|
||||||
|
"With practice comes confidence.",
|
||||||
|
"This is happy place, little squirrels live here and play.",
|
||||||
|
"We have no limits to our world. We're only limited by our imagination.",
|
||||||
|
"Be so very light. Be a gentle whisper.",
|
||||||
|
"The least little bit can do so much.",
|
||||||
|
"All you have to do is let your imagination go wild.",
|
||||||
|
"Didn't you know you had that much power? You can move mountains. You can do anything.",
|
||||||
|
"If you don't like it - change it. It's your world.",
|
||||||
|
"There is immense joy in just watching - watching all the little creatures in nature.",
|
||||||
|
"Don't forget to tell these special people in your life just how special they are to you.",
|
||||||
|
"That is when you can experience true joy, when you have no fear.",
|
||||||
|
"We don't really know where this goes - and I'm not sure we really care.",
|
||||||
|
"Life is too short to be alone, too precious. Share it with a friend.",
|
||||||
|
"Work on one thing at a time. Don't get carried away - we have plenty of time.",
|
||||||
|
"If we're going to have animals around we all have to be concerned about them and take care of them.",
|
||||||
|
"Almost everything is going to happen for you automatically - you don't have to spend any time working or worrying.",
|
||||||
|
"Sometimes you learn more from your mistakes than you do from your masterpieces.",
|
||||||
|
"There are no limits in this world.",
|
||||||
|
"You can create anything that makes you happy.",
|
||||||
|
"You want your tree to have some character. Make it special.",
|
||||||
|
"In your world you have total and absolute power.",
|
||||||
|
"The man who does the best job is the one who is happy at his job.",
|
||||||
|
"Everything's not great in life, but we can still find beauty in it.",
|
||||||
|
"Don't hurry. Take your time and enjoy.",
|
||||||
|
"You're the greatest thing that has ever been or ever will be. You're special. You're so very special.",
|
||||||
|
"There is no right or wrong - as long as it makes you happy and doesn't hurt anyone.",
|
||||||
|
"Everyone needs a friend. Friends are the most valuable things in the world.",
|
||||||
|
"There are no mistakes. You can fix anything that happens.",
|
||||||
|
"That's why I paint - because I can create the kind of world I want - and I can make this world as happy as I want it.",
|
||||||
|
"It's life. It's interesting. It's fun.",
|
||||||
|
"Just think about these things in your mind - then bring them into your world.",
|
||||||
|
"In your imagination you can go anywhere you want.",
|
||||||
|
"That's what makes life fun. That you can make these decisions. That you can create the world that you want.",
|
||||||
|
"Go out on a limb - that's where the fruit is.",
|
||||||
|
"In this world, everything can be happy.",
|
||||||
|
"All you have to learn here is how to have fun.",
|
||||||
|
"Isn't it great to do something you can't fail at?",
|
||||||
|
"Anytime you learn something your time and energy are not wasted.",
|
||||||
|
"A tree cannot be straight if it has a crooked trunk.",
|
||||||
|
"You've got to learn to fight the temptation to resist these things. Just let them happen.",
|
||||||
|
"You create the dream - then you bring it into your world.",
|
||||||
|
"These things happen automatically. All you have to do is just let them happen.",
|
||||||
|
"You can do anything here. So don't worry about it.",
|
||||||
|
"This present moment is perfect simply due to the fact you're experiencing it.",
|
||||||
|
"Only God can make a tree - but you can paint one.",
|
||||||
|
"You can't make a mistake. Anything that happens you can learn to use - and make something beautiful out of it.",
|
||||||
|
"Put light against light - you have nothing. Put dark against dark - you have nothing. It's the contrast of light and dark that each give the other one meaning.",
|
||||||
|
"This is an example of what you can do with just a few things, a little imagination and a happy dream in your heart.",
|
||||||
|
"If what you're doing doesn't make you happy - you're doing the wrong thing.",
|
||||||
|
"Just relax and let it flow. That easy.",
|
||||||
|
"Every single thing in the world has its own personality - and it is up to you to make friends with the little rascals.",
|
||||||
|
"Trees grow however makes them happy.",
|
||||||
|
"Don't kill all your dark areas - you need them to show the light.",
|
||||||
|
"Everyone wants to enjoy the good parts - but you have to build the framework first.",
|
||||||
|
"A big strong tree needs big strong roots.",
|
||||||
|
"The light is your friend. Preserve it.",
|
||||||
|
"Everybody's different. Trees are different. Let them all be individuals.",
|
||||||
|
"We want to use a lot pressure while using no pressure at all.",
|
||||||
|
"Follow the lay of the land. It's most important.",
|
||||||
|
"We don't have anything but happy trees here.",
|
||||||
|
"Even trees need a friend. We all need friends.",
|
||||||
|
"Isn't it fantastic that you can change your mind and create all these happy things?",
|
||||||
|
"The first step to doing anything is to believe you can do it. See it finished in your mind before you ever start.",
|
||||||
|
"No worries. No cares. Just float and wait for the wind to blow you around.",
|
||||||
|
"It just happens - whether or not you worried about it or tried to plan it.",
|
||||||
|
"If it's not what you want - stop and change it. Don't just keep going and expect it will get better.",
|
||||||
|
"You can create the world you want to see and be a part of. You have that power.",
|
||||||
|
"How to paint. That's easy. What to paint. That's much harder.",
|
||||||
|
"We can always carry this a step further. There's really no end to this.",
|
||||||
|
"But we're not there yet, so we don't need to worry about it.",
|
||||||
|
"This is your creation - and it's just as unique and special as you are.",
|
||||||
|
"La- da- da- da- dah. Just be happy.",
|
||||||
|
"Just take out whatever you don't want. It'll change your entire perspective.",
|
||||||
|
"If you don't think every day is a good day - try missing a few. You'll see.",
|
||||||
|
"There isn't a rule. You just practice and find out which way works best for you.",
|
||||||
|
"Just let your mind wander and enjoy. This should make you happy.",
|
||||||
|
"We don't have to be committed. We are just playing here.",
|
||||||
|
"Remember how free clouds are. They just lay around in the sky all day long.",
|
||||||
|
"Any little thing can be your friend if you let it be.",
|
||||||
|
"The very fact that you're aware of suffering is enough reason to be overjoyed that you're alive and can experience it.",
|
||||||
|
"And that's when it becomes fun - you don't have to spend your time thinking about what's happening - you just let it happen.",
|
||||||
|
"This is probably the greatest thing to happen in my life - to be able to share this with you.",
|
||||||
|
"If there are two big trees, eventually there will be a little tree.",
|
||||||
|
"Everybody needs a friend.",
|
||||||
|
"You can do anything here - the only pre-requisite is that it makes you happy.",
|
||||||
|
"The more we do this - the more it will do good things to our heart.",
|
||||||
|
"Even the worst thing we can do here is good.",
|
||||||
|
"Absolutely no pressure. You are just a whisper floating across a mountain.",
|
||||||
|
"You have freedom here. The only guide is your heart.",
|
||||||
|
"Trees grow in all kinds of ways. They're not all perfectly straight. Not every limb is perfect.",
|
||||||
|
"We must be quiet, soft and gentle.",
|
||||||
|
"You can't have light without dark. You can't know happiness unless you've known sorrow.",
|
||||||
|
"Just let this happen. We just let this flow right out of our minds.",
|
||||||
|
"Just make a decision and let it go.",
|
||||||
|
"Everything is happy if you choose to make it that way.",
|
||||||
|
"You could sit here for weeks with your one hair brush trying to do that - or you could do it with one stroke with an almighty brush.",
|
||||||
|
"We're not trying to teach you a thing to copy. We're just here to teach you a technique, then let you loose into the world.",
|
||||||
|
"A tree needs to be your friend if you're going to paint him.",
|
||||||
|
"You have to make almighty decisions when you're the creator.",
|
||||||
|
"This is your world, whatever makes you happy you can put in it. Go crazy.",
|
||||||
|
"We don't have to be concerned about it. We just have to let it fall where it will.",
|
||||||
|
"We don't need any guidelines or formats. All we need to do is just let it flow right out of us.",
|
||||||
|
"Let all these little things happen. Don't fight them. Learn to use them.",
|
||||||
|
"I sincerely wish for you every possible joy life could bring.",
|
||||||
|
"We spend so much of our life looking - but never seeing.",
|
||||||
|
"Talent is a pursued interest. That is to say, anything you practice you can do.",
|
||||||
|
"Let's make some happy little clouds in our world"
|
||||||
|
]
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,16 @@
|
||||||
|
This guy Lucas has got us all by the balls. His fingers are in our wallets. Get your finger out of my ass wallet!
|
||||||
|
The Millennium Falcon is safe. It was not raped. Where would you rape the Millennium Falcon anyway?
|
||||||
|
I'm gonna fuck my cat and eat my cat and I'm gonna kidnap a hooker, and it'll help me fuck the pain away.
|
||||||
|
Now I've analyzed this movie with a team of cheerleaders, who all came to one unanymous conclusion: that if I let them go, they won't tell nobody.
|
||||||
|
What red-blooded male wouldn't want to dock his canoe in Natalie's port, man?
|
||||||
|
Empire pulled this off perfectly, of course. 'Cause I love Empire so much I fuck it.
|
||||||
|
Oh, my pizza rolls is done. You want some pizza rolls? Are ya sure? They're really good pizza rolls. They're hot and pizza-y.
|
||||||
|
This is what they call filler, and it's nowhere near as good as the kind they put in Twinkies. Mmmmm...I like to fuck my cat.
|
||||||
|
Paging Dr. Plinkett. Dr. Plinkett is in, I'm here. Somebody pass the Vicodin. No, wait, we need Ambien. Well, we sure need some-thien.
|
||||||
|
And someone even said it was the bestest movie ever because it had lava in it. Aw, ain't he cute? His name is Johnny. I adopted him, from a grocery store parking lot.
|
||||||
|
Even Ray Charles could see that coming, and he doesn't know anything about Star Wars.
|
||||||
|
There are two types of people in this world: people that understand what I'm saying, and people that like the Star Wars prequels.
|
||||||
|
Now, is General Grievous supposed to be funny? Cause they said he was a villain, not a comedian, like Larry Seinfeld. But rather a creepy weirdo, like Jerry Flint. I'm so confused.
|
||||||
|
Baby's Day Out is about as interesting as my taint.
|
||||||
|
When will I get my merkins in the mail?
|
||||||
|
Anybody wanna help me milk my cock? http://i.imgur.com/vvfu4Y5.jpg
|
|
@ -0,0 +1,8 @@
|
||||||
|
from util import hook
|
||||||
|
from ddate.base import DDate
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
@hook.command
|
||||||
|
def ddate(inp):
|
||||||
|
return str(DDate())
|
|
@ -0,0 +1,11 @@
|
||||||
|
from util import hook
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
@hook.command
|
||||||
|
def derpiback(pls):
|
||||||
|
r = requests.get("https://derpibooru.org")
|
||||||
|
if "J6-eVNTVvMk" in r.text:
|
||||||
|
return "nope derpibooru is still down for maintenance, at soonest it will be tomorrow"
|
||||||
|
|
||||||
|
return "yep"
|
|
@ -0,0 +1,315 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
import sys, os
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
import re
|
||||||
|
|
||||||
|
choice = random.choice
|
||||||
|
randint = random.randint
|
||||||
|
|
||||||
|
def dc(n, m=20):
|
||||||
|
return n < randint(0, m-1)
|
||||||
|
|
||||||
|
base_noun = [
|
||||||
|
'TCP', 'IP', 'UDP', 'BGP', 'DNS', 'ARP spoof', 'ARP', 'JavaScript',
|
||||||
|
'HTML', 'CSS', 'XML', 'SOAP', 'REST', 'SSL', 'socket', 'BSD', 'linux',
|
||||||
|
'MPI', 'OpenMP', 'SYN/ACK', 'kernel', 'ELF', 'COFF', '68000', 'x86',
|
||||||
|
'MIPS', 'ethernet', 'MAC', 'C', 'C++', 'Java', 'JSON', 'ruby',
|
||||||
|
'python', 'linked list', 'radix trie', 'hash table', 'SQL', 'makefile',
|
||||||
|
'/proc', '/dev/null', 'tty', 'regex', 'sed', 'vim', 's/// operation',
|
||||||
|
'operation', 'port scanner', 'port scan', 'lookup table', 'anti-<noun>',
|
||||||
|
'<verber> manual', '<verber> config', 'IRC', 'IRC bot', 'bootloader',
|
||||||
|
'GNU/<noun>',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# I suppose it would be easier to create rules for a lot of these, but
|
||||||
|
# I think LUTs are fine.
|
||||||
|
|
||||||
|
base_verb = [
|
||||||
|
( 'compile', '-s', '-d', '-r', 'compiling' ),
|
||||||
|
( 'link', '-s', '-ed', '-er', '-ing' ),
|
||||||
|
( 'assemble', '-s', '-d', '-r', 'assembling' ),
|
||||||
|
( 'load', '-s', '-ed', '-er', '-ing' ),
|
||||||
|
( 'boot', '-s', '-ed', '-er', '-ing' ),
|
||||||
|
( 'reset', '-s', 'reset', '-ter', '-ting' ),
|
||||||
|
( 'remove', '-s', '-d', '-r', 'removing' ),
|
||||||
|
( 'decompile', '-s', '-d', '-r', 'decompiling' ),
|
||||||
|
( 'unlink', '-s', '-ed', '-er', '-ing' ),
|
||||||
|
( 'disassemble', '-s', '-d', '-r', 'disassembling' ),
|
||||||
|
( 'unload', '-s', '-ed', '-er', '-ing' ),
|
||||||
|
( 'parse', '-s', '-d', '-r', 'parsing' ),
|
||||||
|
( 'archive', '-s', '-d', '-r', 'archiving' ),
|
||||||
|
( 'cherry-pick', '-s', '-ed', '-er', '-ing' ),
|
||||||
|
( 'overwrite', '-s', 'overwrote', '-r', 'overwriting' ),
|
||||||
|
( 'edit', '-s', '-ed', '-or', '-ing' ),
|
||||||
|
( 'compute', '-s', '-d', '-r', 'computing' ),
|
||||||
|
( 'release', '-s', '-d', '-r', 'releasing' ),
|
||||||
|
( 'transmit', '-s', '-ted', '-ter', '-ting' ),
|
||||||
|
( 'receive', '-s', '-d', '-r', 'receiving' ),
|
||||||
|
( 'analyze', '-s', '-d', '-r', 'analyzing' ),
|
||||||
|
( 'print', '-s', '-ed', '-er', '-ing' ),
|
||||||
|
( 'save', '-s', '-d', '-r', 'saving' ),
|
||||||
|
( 'erase', '-s', '-d', '-r', 'erasing' ),
|
||||||
|
( 'install', '-s', '-ed', '-er', '-ing' ),
|
||||||
|
( 'scan', '-s', '-ned', '-ner', '-ning' ),
|
||||||
|
( 'port scan', '-s', '-ned', '-ner', '-ning' ),
|
||||||
|
( 'nmap', '-s', '-ped', '-per', '-ping' ),
|
||||||
|
( 'DDOS', '-es', '-sed', '-ser', '-sing' ),
|
||||||
|
( 'exploit', '-s', '-ed', '-er', '-ing' ),
|
||||||
|
( 'send', '-s', 'sent', '-er', '-ing' ),
|
||||||
|
( 'write', '-s', 'wrote', '-r', 'writing' ),
|
||||||
|
( 'detect', '-s', '-ed', '-or', '-ing' ),
|
||||||
|
( 'sniff', '-s', '-ed', '-er', '-ing' ),
|
||||||
|
|
||||||
|
( 'look up', 'looks up', 'looked up', 'looker upper', 'looking up' ),
|
||||||
|
( 'check out', 'checks out', 'checked out', 'checker outer', 'checking out' ),
|
||||||
|
( 'query', 'queries', 'queried', 'querier', 'querying' ),
|
||||||
|
]
|
||||||
|
|
||||||
|
base_service = [
|
||||||
|
'Google', 'Amazon', 'Stack Overflow', 'Freenode', 'EFnet', 'Usenet',
|
||||||
|
'this old GeoCities page', 'my website', '<person>\'s website',
|
||||||
|
]
|
||||||
|
|
||||||
|
base_hack_object = [
|
||||||
|
'the <noun>', 'a(n) <noun>', 'the victim\'s <noun>', 'some <noun>',
|
||||||
|
'a(n) <verber> from <service>', '<service>\'s <noun>',
|
||||||
|
'the freeware <noun>', 'a configurable <noun>', 'a working <noun>',
|
||||||
|
'a pre-<verbed> <verber>',
|
||||||
|
]
|
||||||
|
|
||||||
|
base_tool = [
|
||||||
|
'<noun> <verber>',
|
||||||
|
'<verbed> <noun>',
|
||||||
|
'<verber>',
|
||||||
|
'<verber> for <nouns>',
|
||||||
|
'<verbing> <tool>',
|
||||||
|
'thing that <verbs>',
|
||||||
|
'thing for <verbing> <nouns>',
|
||||||
|
'pre-<noun> <verber>',
|
||||||
|
'anti-<noun> <verber>',
|
||||||
|
'<verbing> tool',
|
||||||
|
'<verber> subsystem',
|
||||||
|
'professional <verber>',
|
||||||
|
'<verber>-<verber> hybrid',
|
||||||
|
]
|
||||||
|
|
||||||
|
base_tools = [
|
||||||
|
'<noun> <verber>s',
|
||||||
|
'<verbed> <nouns>',
|
||||||
|
'<verber>s',
|
||||||
|
'<verbing> <tools>',
|
||||||
|
'things for <verbing>',
|
||||||
|
'pre-<noun> <verber>s',
|
||||||
|
]
|
||||||
|
|
||||||
|
base_person = [
|
||||||
|
'Linus Torvalds',
|
||||||
|
'Alan Cox',
|
||||||
|
'Con Colivas',
|
||||||
|
'Ingo Molnar',
|
||||||
|
'Hans Reiser',
|
||||||
|
'Ulrich Drepper',
|
||||||
|
'Larry Wall',
|
||||||
|
'William Pitcock',
|
||||||
|
'Bill Gates',
|
||||||
|
'Ken Thompson',
|
||||||
|
'Brian Khernigan',
|
||||||
|
'Dennis Ritchie',
|
||||||
|
'Eric S. Raymond',
|
||||||
|
'Richard M. Stallman',
|
||||||
|
'DPR',
|
||||||
|
'Sabu',
|
||||||
|
]
|
||||||
|
|
||||||
|
base_system = [
|
||||||
|
'Amiga', 'C-64', 'IBM PC', 'Z80', 'VAX', 'the PDP-8',
|
||||||
|
]
|
||||||
|
|
||||||
|
base_time = [
|
||||||
|
'way back', 'a few years ago', 'in the early 90\'s I think',
|
||||||
|
'when everybody had a(n) <verber>',
|
||||||
|
'before anybody knew who <person> was',
|
||||||
|
]
|
||||||
|
|
||||||
|
# <hack> is intransitive
|
||||||
|
# <tool> is singular
|
||||||
|
|
||||||
|
base_advice = [
|
||||||
|
'Try <hacking>.',
|
||||||
|
'Did you <hack> first?',
|
||||||
|
'Read up on <hacking>.',
|
||||||
|
'Check <service> for a(n) <tool>.',
|
||||||
|
'See if the <tool> has <hacked> already.',
|
||||||
|
'Did you check the <tool> config?',
|
||||||
|
'Hm, sounds like a problem with the <tool>.',
|
||||||
|
'Doesn\'t look like the <tool> is <hacking>.',
|
||||||
|
'Check the "<tool>" wiki.',
|
||||||
|
'You probably didn\'t <hack>.',
|
||||||
|
'Check the "<tool>" website.',
|
||||||
|
'<hack>, then send me the <tool> output.',
|
||||||
|
'Pastebin your <tool> config.',
|
||||||
|
'I think my <noun> has a(n) <verber>, try that.',
|
||||||
|
'<hacking> worked for me.',
|
||||||
|
'Did you enable the <tool>?',
|
||||||
|
'No, the <tool> <hacks>. You want a(n) <tool>.',
|
||||||
|
'Do you have a(n) <tool> installed?',
|
||||||
|
'A(n) <tool> is needed to <hack>.',
|
||||||
|
'<person> claims you can <hack>.',
|
||||||
|
'I heard <person> <hacks> when that happens.',
|
||||||
|
'I saw on <service>, you can <hack>.',
|
||||||
|
'A(n) <tool> might do the trick.',
|
||||||
|
'Make sure to delete your <tool>. That stuff is illegal.',
|
||||||
|
'Did you <hack> before you <hacked>?',
|
||||||
|
'Where did you <verb> the <tool> to?',
|
||||||
|
'I don\'t know. Ask the guy who wrote your <tool>. I think <person>?',
|
||||||
|
'Was this with a(n) <tool> or a(n) <tool>?',
|
||||||
|
'Please use the official <tool>.',
|
||||||
|
'That won\'t work. You can\'t just <hack>.',
|
||||||
|
'<hack>, <hack>, and THEN <hack>. Sheesh.',
|
||||||
|
'No, don\'t <hack>. <person> recently published a CVE about that.',
|
||||||
|
'<verb>, <verb>, <verb>. This is our motto.',
|
||||||
|
'Don\'t think too hard about <hacking>. The <tool> will do that.',
|
||||||
|
'There\'s a(n) <noun> exploit floating around somewhere. Check <service>.',
|
||||||
|
'Simple <tools> cannot <hack>. You need a good, solid <tool>.',
|
||||||
|
'I had a(n) <tool> for <system> <time>.',
|
||||||
|
'Sounds like you need a(n) <tool>. <person> wrote one for <service>.',
|
||||||
|
]
|
||||||
|
|
||||||
|
def simple_get(the_list):
|
||||||
|
def get_thing():
|
||||||
|
return choice(the_list)
|
||||||
|
return get_thing
|
||||||
|
|
||||||
|
get_base_verb = simple_get(base_verb)
|
||||||
|
get_noun = simple_get(base_noun)
|
||||||
|
get_service = simple_get(base_service)
|
||||||
|
get_hack_object = simple_get(base_hack_object)
|
||||||
|
get_tool = simple_get(base_tool)
|
||||||
|
get_tools = simple_get(base_tools)
|
||||||
|
get_person = simple_get(base_person)
|
||||||
|
get_base_advice = simple_get(base_advice)
|
||||||
|
get_system = simple_get(base_system)
|
||||||
|
get_time = simple_get(base_time)
|
||||||
|
|
||||||
|
def get_nouns():
|
||||||
|
n = get_noun()
|
||||||
|
if not n[-1] in string.ascii_letters:
|
||||||
|
return n + '\'s'
|
||||||
|
if n.lower()[-1] in 'xs':
|
||||||
|
return n + 'es'
|
||||||
|
return n + 's'
|
||||||
|
|
||||||
|
def compute_verb(n):
|
||||||
|
v = get_base_verb()
|
||||||
|
base = v[0]
|
||||||
|
ext = v[n]
|
||||||
|
if ext[0] == '-':
|
||||||
|
return base + ext[1:]
|
||||||
|
return ext
|
||||||
|
|
||||||
|
def make_verb(n):
|
||||||
|
def get_verb():
|
||||||
|
return compute_verb(n)
|
||||||
|
return get_verb
|
||||||
|
|
||||||
|
get_verb = make_verb(0)
|
||||||
|
get_verbs = make_verb(1)
|
||||||
|
get_verbed = make_verb(2)
|
||||||
|
get_verber = make_verb(3)
|
||||||
|
get_verbing = make_verb(4)
|
||||||
|
|
||||||
|
def make_hack(vf):
|
||||||
|
def get_hack():
|
||||||
|
return vf() + ' ' + get_hack_object()
|
||||||
|
return get_hack
|
||||||
|
|
||||||
|
def get_hacker():
|
||||||
|
return get_hack_object() + ' ' + get_verber()
|
||||||
|
|
||||||
|
get_hack = make_hack(get_verb)
|
||||||
|
get_hacks = make_hack(get_verbs)
|
||||||
|
get_hacked = make_hack(get_verbed)
|
||||||
|
get_hacking = make_hack(get_verbing)
|
||||||
|
|
||||||
|
index_get = {
|
||||||
|
'verb': get_verb,
|
||||||
|
'verbs': get_verbs,
|
||||||
|
'verbed': get_verbed,
|
||||||
|
'verber': get_verber,
|
||||||
|
'verbing': get_verbing,
|
||||||
|
|
||||||
|
'noun': get_noun,
|
||||||
|
'nouns': get_nouns,
|
||||||
|
|
||||||
|
'hack': get_hack,
|
||||||
|
'hacks': get_hacks,
|
||||||
|
'hacked': get_hacked,
|
||||||
|
'hacker': get_hacker,
|
||||||
|
'hacking': get_hacking,
|
||||||
|
|
||||||
|
'service': get_service,
|
||||||
|
|
||||||
|
'tool': get_tool,
|
||||||
|
'tools': get_tools,
|
||||||
|
|
||||||
|
'person': get_person,
|
||||||
|
'system': get_system,
|
||||||
|
'time': get_time,
|
||||||
|
}
|
||||||
|
|
||||||
|
def can_reduce(s):
|
||||||
|
return '<' in s and '>' in s
|
||||||
|
|
||||||
|
def reduction(s):
|
||||||
|
s = re.split('[<>]', s)
|
||||||
|
for i, word in enumerate(s):
|
||||||
|
if i % 2 == 0:
|
||||||
|
continue
|
||||||
|
up = False
|
||||||
|
if all(x in string.ascii_uppercase for x in word):
|
||||||
|
up = True
|
||||||
|
word = word.lower()
|
||||||
|
if not word in index_get:
|
||||||
|
word = '?' + word
|
||||||
|
else:
|
||||||
|
word = index_get[word]()
|
||||||
|
if up:
|
||||||
|
word = word.upper()
|
||||||
|
s[i] = word
|
||||||
|
return ''.join(s)
|
||||||
|
|
||||||
|
def indefinite_articles(s):
|
||||||
|
s = re.sub('([aA])\\(([nN])\\) ([AEFHILMNORSXaeiou])', '\\1\\2 \\3', s)
|
||||||
|
s = re.sub('([aA])\\([nN]\\) ', '\\1 ', s)
|
||||||
|
return s
|
||||||
|
|
||||||
|
def evaluate(s):
|
||||||
|
while can_reduce(s):
|
||||||
|
s = reduction(s)
|
||||||
|
s = indefinite_articles(s)
|
||||||
|
s = s[0].upper() + s[1:]
|
||||||
|
return s
|
||||||
|
|
||||||
|
def get_advice():
|
||||||
|
return evaluate(get_base_advice())
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
print(evaluate(sys.argv[1]))
|
||||||
|
else:
|
||||||
|
print(get_advice())
|
||||||
|
|
||||||
|
from util import hook
|
||||||
|
|
||||||
|
@hook.command
|
||||||
|
def hack(inp):
|
||||||
|
res = ""
|
||||||
|
if len(inp) > 1:
|
||||||
|
res = evaluate(inp)
|
||||||
|
else:
|
||||||
|
res = evaluate(get_base_advice())
|
||||||
|
|
||||||
|
return res
|
|
@ -0,0 +1,103 @@
|
||||||
|
"""
|
||||||
|
log.py: written by Scaevolus 2009
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import codecs
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
|
||||||
|
from util import hook
|
||||||
|
|
||||||
|
|
||||||
|
log_fds = {} # '%(net)s %(chan)s' : (filename, fd)
|
||||||
|
|
||||||
|
timestamp_format = '%H:%M:%S'
|
||||||
|
|
||||||
|
formats = {'PRIVMSG': '<%(nick)s> %(msg)s',
|
||||||
|
'PART': '-!- %(nick)s [%(user)s@%(host)s] has left %(chan)s',
|
||||||
|
'JOIN': '-!- %(nick)s [%(user)s@%(host)s] has joined %(param0)s',
|
||||||
|
'MODE': '-!- mode/%(chan)s [%(param_tail)s] by %(nick)s',
|
||||||
|
'KICK': '-!- %(param1)s was kicked from %(chan)s by %(nick)s [%(msg)s]',
|
||||||
|
'TOPIC': '-!- %(nick)s changed the topic of %(chan)s to: %(msg)s',
|
||||||
|
'QUIT': '-!- %(nick)s has quit [%(msg)s]',
|
||||||
|
'PING': '',
|
||||||
|
'NOTICE': ''
|
||||||
|
}
|
||||||
|
|
||||||
|
ctcp_formats = {'ACTION': '* %(nick)s %(ctcpmsg)s'}
|
||||||
|
|
||||||
|
irc_color_re = re.compile(r'(\x03(\d+,\d+|\d)|[\x0f\x02\x16\x1f])')
|
||||||
|
|
||||||
|
|
||||||
|
def get_log_filename(dir, server, chan):
|
||||||
|
return os.path.join(dir, 'log', gmtime('%Y'), server,
|
||||||
|
(gmtime('%%s.%m-%d.log') % chan).lower())
|
||||||
|
|
||||||
|
|
||||||
|
def gmtime(format):
|
||||||
|
return time.strftime(format, time.gmtime())
|
||||||
|
|
||||||
|
|
||||||
|
def beautify(input):
|
||||||
|
format = formats.get(input.command, '%(raw)s')
|
||||||
|
args = dict(input)
|
||||||
|
|
||||||
|
leng = len(args['paraml'])
|
||||||
|
for n, p in enumerate(args['paraml']):
|
||||||
|
args['param' + str(n)] = p
|
||||||
|
args['param_' + str(abs(n - leng))] = p
|
||||||
|
|
||||||
|
args['param_tail'] = ' '.join(args['paraml'][1:])
|
||||||
|
args['msg'] = irc_color_re.sub('', args['msg'])
|
||||||
|
|
||||||
|
if input.command == 'PRIVMSG' and input.msg.count('\x01') >= 2:
|
||||||
|
ctcp = input.msg.split('\x01', 2)[1].split(' ', 1)
|
||||||
|
if len(ctcp) == 1:
|
||||||
|
ctcp += ['']
|
||||||
|
args['ctcpcmd'], args['ctcpmsg'] = ctcp
|
||||||
|
format = ctcp_formats.get(args['ctcpcmd'],
|
||||||
|
'%(nick)s [%(user)s@%(host)s] requested unknown CTCP '
|
||||||
|
'%(ctcpcmd)s from %(chan)s: %(ctcpmsg)s')
|
||||||
|
|
||||||
|
return format % args
|
||||||
|
|
||||||
|
|
||||||
|
def get_log_fd(dir, server, chan):
|
||||||
|
fn = get_log_filename(dir, server, chan)
|
||||||
|
cache_key = '%s %s' % (server, chan)
|
||||||
|
filename, fd = log_fds.get(cache_key, ('', 0))
|
||||||
|
|
||||||
|
if fn != filename: # we need to open a file for writing
|
||||||
|
if fd != 0: # is a valid fd
|
||||||
|
fd.flush()
|
||||||
|
fd.close()
|
||||||
|
dir = os.path.split(fn)[0]
|
||||||
|
if not os.path.exists(dir):
|
||||||
|
os.makedirs(dir)
|
||||||
|
fd = codecs.open(fn, 'a', 'utf-8')
|
||||||
|
log_fds[cache_key] = (fn, fd)
|
||||||
|
|
||||||
|
return fd
|
||||||
|
|
||||||
|
|
||||||
|
@hook.singlethread
|
||||||
|
@hook.event('*')
|
||||||
|
def log(paraml, input=None, bot=None):
|
||||||
|
timestamp = gmtime(timestamp_format)
|
||||||
|
|
||||||
|
if input.command == 'QUIT': # these are temporary fixes until proper
|
||||||
|
input.chan = 'quit' # presence tracking is implemented
|
||||||
|
if input.command == 'NICK':
|
||||||
|
input.chan = 'nick'
|
||||||
|
|
||||||
|
beau = beautify(input)
|
||||||
|
|
||||||
|
if beau == '': # don't log this
|
||||||
|
return
|
||||||
|
|
||||||
|
if input.chan:
|
||||||
|
fd = get_log_fd(bot.persist_dir, input.server, input.chan)
|
||||||
|
fd.write(timestamp + ' ' + beau + '\n')
|
||||||
|
|
||||||
|
print timestamp, input.chan, beau.encode('utf8', 'ignore')
|
|
@ -0,0 +1,26 @@
|
||||||
|
from random import choice
|
||||||
|
from random import randint
|
||||||
|
from util import hook
|
||||||
|
|
||||||
|
prefix = []
|
||||||
|
suffix = []
|
||||||
|
|
||||||
|
#Read prefix and suffix lines in
|
||||||
|
with open("./plugins/data/opname_prefix.txt", 'r') as prefixfile:
|
||||||
|
prefix = prefixfile.readlines()
|
||||||
|
with open("./plugins/data/opname_suffix.txt", 'r') as suffixfile:
|
||||||
|
suffix = suffixfile.readlines()
|
||||||
|
|
||||||
|
#Strip lines and prune junk lines
|
||||||
|
for ix in [prefix, suffix]:
|
||||||
|
for junk in range(len(ix)-1, -1, -1):
|
||||||
|
ix[junk] = ix[junk].strip()
|
||||||
|
|
||||||
|
@hook.command
|
||||||
|
def opname(inp):
|
||||||
|
#Create phrase
|
||||||
|
phrase = "OPERATION %s %s %s" % \
|
||||||
|
(choice(prefix), choice(prefix), choice(suffix))
|
||||||
|
|
||||||
|
return phrase.upper()
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
from random import choice
|
||||||
|
from util import hook
|
||||||
|
|
||||||
|
quotes = []
|
||||||
|
|
||||||
|
with open("./plugins/data/plinkett.txt", "r") as fin:
|
||||||
|
quotes = fin.readlines()
|
||||||
|
|
||||||
|
@hook.command
|
||||||
|
@hook.regex("^plinkett fact$")
|
||||||
|
def plinkett(inp):
|
||||||
|
return choice(quotes)
|
|
@ -0,0 +1,53 @@
|
||||||
|
from util import hook
|
||||||
|
from ddate.base import DDate
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import ponyapi
|
||||||
|
|
||||||
|
def get_time(ep):
|
||||||
|
now = datetime.datetime(2006, 1, 1)
|
||||||
|
now = now.now()
|
||||||
|
then = now.fromtimestamp(int(ep[u"air_date"]))
|
||||||
|
td = then-now
|
||||||
|
|
||||||
|
return now, then, td
|
||||||
|
|
||||||
|
@hook.command
|
||||||
|
def when(inp, say=None):
|
||||||
|
#"Shows the countdown to the new episode of My Little Pony: Friendship is Magic!"
|
||||||
|
|
||||||
|
try:
|
||||||
|
ep = ponyapi.newest()
|
||||||
|
now, then, td = get_time(ep)
|
||||||
|
seasonep = ""
|
||||||
|
|
||||||
|
if inp == "discord":
|
||||||
|
return "%s will air on %s" % (ep[u"name"], DDate(then))
|
||||||
|
|
||||||
|
if ep[u"is_movie"]:
|
||||||
|
seasonep = "(a movie)"
|
||||||
|
else:
|
||||||
|
seasonep = "(season %d episode %d)" % (ep[u"season"], ep[u"episode"])
|
||||||
|
|
||||||
|
reply = "%s %s will air on %s in %d days!" % (
|
||||||
|
ep[u"name"], seasonep, then.strftime("%a, %d %b %Y %H:%M:%S"),
|
||||||
|
td.days)
|
||||||
|
|
||||||
|
return reply
|
||||||
|
|
||||||
|
except:
|
||||||
|
return "404! We're on hiatus!"
|
||||||
|
|
||||||
|
@hook.command
|
||||||
|
def randomep(inp):
|
||||||
|
#"Shows a random episode of My Little Pony: Friendship is Magic"
|
||||||
|
ep = ponyapi.random()
|
||||||
|
|
||||||
|
seasonep = ""
|
||||||
|
|
||||||
|
if ep[u"is_movie"]:
|
||||||
|
seasonep = "(a movie)"
|
||||||
|
else:
|
||||||
|
seasonep = "(season %d episode %d)" % (ep[u"season"], ep[u"episode"])
|
||||||
|
|
||||||
|
return "%s %s" % (ep[u"name"], seasonep)
|
|
@ -0,0 +1,84 @@
|
||||||
|
import requests
|
||||||
|
|
||||||
|
"""
|
||||||
|
# PonyAPI module for Python programs
|
||||||
|
|
||||||
|
This is written in a metaprogramming style.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import ponyapi
|
||||||
|
|
||||||
|
episodes = ponyapi.all_episodes()
|
||||||
|
|
||||||
|
for episode in episodes:
|
||||||
|
print episode
|
||||||
|
```
|
||||||
|
|
||||||
|
Available methods:
|
||||||
|
|
||||||
|
all_episodes() -> return all information on all episodes
|
||||||
|
newest() -> return information on the newest episode
|
||||||
|
random() -> return a random episode
|
||||||
|
get_season(snum) -> return all episodes in season snum
|
||||||
|
get_episode(snum, enum) -> return info on season snum episode enum
|
||||||
|
search(query) -> return all episodes that have query in the title
|
||||||
|
"""
|
||||||
|
|
||||||
|
API_ENDPOINT = "http://ponyapi.apps.xeserv.us"
|
||||||
|
|
||||||
|
# _base_get :: Text -> Maybe [Text] -> (Maybe [Text] -> IO (Either Episode [Episode]))
|
||||||
|
# _base_get takes a text, a splatted list of texts and returns a function such that
|
||||||
|
# the function takes a splatted list of texts and returns either an Episode or
|
||||||
|
# a list of Episode as an IO action.
|
||||||
|
def _base_get(endpoint, *fragments):
|
||||||
|
def doer(*args):
|
||||||
|
r = None
|
||||||
|
|
||||||
|
assert len(fragments) == len(args)
|
||||||
|
|
||||||
|
if len(fragments) == 0:
|
||||||
|
r = requests.get(API_ENDPOINT + endpoint)
|
||||||
|
else:
|
||||||
|
url = API_ENDPOINT + endpoint
|
||||||
|
|
||||||
|
for i in range(len(fragments)):
|
||||||
|
url = url + "/" + fragments[i] + "/" + str(args[i])
|
||||||
|
|
||||||
|
r = requests.get(url)
|
||||||
|
|
||||||
|
if r.status_code != 200:
|
||||||
|
raise Exception("Not found or server error")
|
||||||
|
|
||||||
|
try:
|
||||||
|
return r.json()["episodes"]
|
||||||
|
except:
|
||||||
|
return r.json()["episode"]
|
||||||
|
|
||||||
|
return doer
|
||||||
|
|
||||||
|
# all_episodes :: IO [Episode]
|
||||||
|
all_episodes = _base_get("/all")
|
||||||
|
|
||||||
|
# newest :: IO Episode
|
||||||
|
newest = _base_get("/newest")
|
||||||
|
|
||||||
|
# random :: IO Episode
|
||||||
|
random = _base_get("/random")
|
||||||
|
|
||||||
|
# get_season :: Int -> IO [Episode]
|
||||||
|
get_season = _base_get("", "season")
|
||||||
|
|
||||||
|
# get_episode :: Int -> Int -> IO Episode
|
||||||
|
get_episode = _base_get("", "season", "episode")
|
||||||
|
|
||||||
|
# search :: Text -> IO [Episode]
|
||||||
|
def search(query):
|
||||||
|
params = {"q": query}
|
||||||
|
r = requests.get(API_ENDPOINT + "/search", params=params)
|
||||||
|
|
||||||
|
if r.status_code != 200:
|
||||||
|
raise Exception("Not found or server error")
|
||||||
|
|
||||||
|
return r.json()["episodes"]
|
|
@ -0,0 +1,19 @@
|
||||||
|
from util import hook
|
||||||
|
|
||||||
|
import requests, re, random
|
||||||
|
|
||||||
|
regex = re.compile(re.escape("cat"), re.IGNORECASE)
|
||||||
|
kittenrex = re.compile(re.escape("kitten"), re.IGNORECASE)
|
||||||
|
preggorex = re.compile(re.escape("pregmant"), re.IGNORECASE)
|
||||||
|
|
||||||
|
@hook.regex("PHP sadness$")
|
||||||
|
def php_fact(inp):
|
||||||
|
return "http://phpsadness.com/sad/" + str(random.randint(0,53))
|
||||||
|
|
||||||
|
@hook.regex("^printer fact$")
|
||||||
|
@hook.command
|
||||||
|
def printerfact(inp, say=None):
|
||||||
|
r = requests.get('https://catfacts-api.appspot.com/api/facts?number=1')
|
||||||
|
fact = r.json()['facts'][0]
|
||||||
|
inp = "printer"
|
||||||
|
return kittenrex.sub("baby "+ inp, regex.sub(inp, fact))
|
|
@ -0,0 +1,193 @@
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from util import hook
|
||||||
|
|
||||||
|
|
||||||
|
def add_quote(db, chan, nick, add_nick, msg):
|
||||||
|
db.execute('''insert or fail into quote (chan, nick, add_nick,
|
||||||
|
msg, time) values(?,?,?,?,?)''',
|
||||||
|
(chan, nick, add_nick, msg, time.time()))
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def del_quote(db, chan, nick, msg):
|
||||||
|
updated = db.execute('''update quote set deleted = 1 where
|
||||||
|
chan=? and lower(nick)=lower(?) and msg=?''',
|
||||||
|
(chan, nick, msg))
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
if updated.rowcount == 0:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def get_quotes_by_nick(db, chan, nick):
|
||||||
|
return db.execute("select time, nick, msg from quote where deleted!=1 "
|
||||||
|
"and chan=? and lower(nick)=lower(?) order by time",
|
||||||
|
(chan, nick)).fetchall()
|
||||||
|
|
||||||
|
|
||||||
|
def get_quotes_by_chan(db, chan):
|
||||||
|
return db.execute("select time, nick, msg from quote where deleted!=1 "
|
||||||
|
"and chan=? order by time", (chan,)).fetchall()
|
||||||
|
|
||||||
|
|
||||||
|
def get_quote_by_id(db, num):
|
||||||
|
return db.execute("select time, nick, msg from quote where deleted!=1 "
|
||||||
|
"and rowid=?", (num,)).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(inp, nick='', chan='', db=None, admin=False):
|
||||||
|
".q/.quote [#chan] [nick] [#n]/.quote add|delete <nick> <msg> -- gets " \
|
||||||
|
"random or [#n]th quote by <nick> or from <#chan>/adds or deletes " \
|
||||||
|
"quote"
|
||||||
|
|
||||||
|
db.execute("create table if not exists quote"
|
||||||
|
"(chan, nick, add_nick, msg, time real, deleted default 0, "
|
||||||
|
"primary key (chan, nick, msg))")
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
add = re.match(r"add[^\w@]+(\S+?)>?\s+(.*)", inp, re.I)
|
||||||
|
delete = re.match(r"delete[^\w@]+(\S+?)>?\s+(.*)", inp, re.I)
|
||||||
|
retrieve = re.match(r"(\S+)(?:\s+#?(-?\d+))?$", inp)
|
||||||
|
retrieve_chan = re.match(r"(#\S+)\s+(\S+)(?:\s+#?(-?\d+))?$", inp)
|
||||||
|
retrieve_id = re.match(r"(\d+)$", inp)
|
||||||
|
|
||||||
|
if add:
|
||||||
|
quoted_nick, msg = add.groups()
|
||||||
|
try:
|
||||||
|
add_quote(db, chan, quoted_nick, nick, msg)
|
||||||
|
db.commit()
|
||||||
|
except db.IntegrityError:
|
||||||
|
return "message already stored, doing nothing."
|
||||||
|
return "quote added."
|
||||||
|
if delete:
|
||||||
|
if not admin:
|
||||||
|
return 'only admins can delete quotes'
|
||||||
|
quoted_nick, msg = delete.groups()
|
||||||
|
if del_quote(db, chan, quoted_nick, msg):
|
||||||
|
return "deleted quote '%s'" % msg
|
||||||
|
else:
|
||||||
|
return "found no matching quotes to delete"
|
||||||
|
elif retrieve_id:
|
||||||
|
quote_id, = retrieve_id.groups()
|
||||||
|
num = 1
|
||||||
|
quotes = get_quote_by_id(db, quote_id)
|
||||||
|
elif retrieve:
|
||||||
|
select, num = retrieve.groups()
|
||||||
|
if select.startswith('#'):
|
||||||
|
quotes = get_quotes_by_chan(db, select)
|
||||||
|
else:
|
||||||
|
quotes = get_quotes_by_nick(db, chan, select)
|
||||||
|
elif retrieve_chan:
|
||||||
|
chan, nick, num = retrieve_chan.groups()
|
||||||
|
|
||||||
|
quotes = get_quotes_by_nick(db, chan, nick)
|
||||||
|
else:
|
||||||
|
return quote.__doc__
|
||||||
|
|
||||||
|
if num:
|
||||||
|
num = int(num)
|
||||||
|
|
||||||
|
n_quotes = len(quotes)
|
||||||
|
|
||||||
|
if not n_quotes:
|
||||||
|
return "no quotes found"
|
||||||
|
|
||||||
|
if num:
|
||||||
|
if num > n_quotes or (num < 0 and num < -n_quotes):
|
||||||
|
return "I only have %d quote%s for %s" % (n_quotes,
|
||||||
|
('s', '')[n_quotes == 1], select)
|
||||||
|
elif num < 0:
|
||||||
|
selected_quote = quotes[num]
|
||||||
|
num = n_quotes + num + 1
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
class QuoteTest(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
import sqlite3
|
||||||
|
self.db = sqlite3.connect(':memory:')
|
||||||
|
|
||||||
|
quote('', db=self.db) # init DB
|
||||||
|
|
||||||
|
def quote(self, arg, **kwargs):
|
||||||
|
return quote(arg, chan='#test', nick='alice', db=self.db, **kwargs)
|
||||||
|
|
||||||
|
def add_quote(self, msg=''):
|
||||||
|
add_quote(self.db, '#test', 'Socrates', 'Plato',
|
||||||
|
msg or 'Education is the kindling of a flame,'
|
||||||
|
' not the filling of a vessel.')
|
||||||
|
|
||||||
|
def test_retrieve_chan(self):
|
||||||
|
self.add_quote()
|
||||||
|
assert '<Socrates> Education' in self.quote('#test')
|
||||||
|
|
||||||
|
def test_retrieve_user(self):
|
||||||
|
self.add_quote()
|
||||||
|
assert '<Socrates> Education' in self.quote('socrates')
|
||||||
|
|
||||||
|
def test_no_quotes(self):
|
||||||
|
assert "no quotes found" in self.quote("#notachan")
|
||||||
|
|
||||||
|
def test_quote_too_high(self):
|
||||||
|
self.add_quote()
|
||||||
|
assert 'I only have 1 quote for #test' in self.quote('#test 4')
|
||||||
|
|
||||||
|
def test_add(self):
|
||||||
|
self.quote("add <someone> witty phrase")
|
||||||
|
assert 'witty' in self.quote('#test')
|
||||||
|
|
||||||
|
def test_add_twice(self):
|
||||||
|
self.quote("add <someone> lol")
|
||||||
|
assert 'already stored' in self.quote("add <someone> lol")
|
||||||
|
|
||||||
|
def test_del_not_admin(self):
|
||||||
|
assert 'only admins' in self.quote('delete whoever 4')
|
||||||
|
|
||||||
|
def test_del_not_exists(self):
|
||||||
|
assert 'found no matching' in self.quote(
|
||||||
|
'delete whoever 4', admin=True)
|
||||||
|
|
||||||
|
def test_del(self):
|
||||||
|
self.add_quote("hi")
|
||||||
|
assert "deleted quote 'hi'" in self.quote(
|
||||||
|
'delete socrates hi', admin=True)
|
||||||
|
|
||||||
|
def test_retrieve_id(self):
|
||||||
|
self.add_quote()
|
||||||
|
assert 'Education is' in self.quote('1')
|
||||||
|
|
||||||
|
def test_retrieve_chan_user(self):
|
||||||
|
self.add_quote()
|
||||||
|
assert 'Education' in self.quote('#test socrates')
|
||||||
|
assert 'Education' in self.quote('#test socrates 1')
|
||||||
|
|
||||||
|
def test_nth(self):
|
||||||
|
self.add_quote('first quote')
|
||||||
|
self.add_quote('second quote')
|
||||||
|
self.add_quote('third quote')
|
||||||
|
self.add_quote('fourth quote')
|
||||||
|
assert 'third' in self.quote('socrates -2')
|
||||||
|
assert 'only have 4' in self.quote('socrates -9')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
|
@ -0,0 +1,189 @@
|
||||||
|
"""
|
||||||
|
remember.py: written by Scaevolus 2010
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
import string
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from util import hook
|
||||||
|
|
||||||
|
|
||||||
|
def db_init(db):
|
||||||
|
db.execute("create table if not exists memory(chan, word, data, nick,"
|
||||||
|
" primary key(chan, word))")
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def get_memory(db, chan, word):
|
||||||
|
row = db.execute("select data from memory where chan=? and word=lower(?)",
|
||||||
|
(chan, word)).fetchone()
|
||||||
|
if row:
|
||||||
|
return row[0]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@hook.command
|
||||||
|
@hook.command("r")
|
||||||
|
def remember(inp, nick='', chan='', db=None):
|
||||||
|
".remember <word> [+]<data> s/<before>/<after> -- maps word to data in the memory, or "
|
||||||
|
" does a string replacement (not regex)"
|
||||||
|
db_init(db)
|
||||||
|
|
||||||
|
append = False
|
||||||
|
replacement = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
head, tail = inp.split(None, 1)
|
||||||
|
except ValueError:
|
||||||
|
return remember.__doc__
|
||||||
|
|
||||||
|
data = get_memory(db, chan, head)
|
||||||
|
if data is not None:
|
||||||
|
_head, _tail = data.split(None, 1)
|
||||||
|
else:
|
||||||
|
_head, _tail = head, ''
|
||||||
|
|
||||||
|
if tail[0] == '+':
|
||||||
|
append = True
|
||||||
|
# ignore + symbol
|
||||||
|
new = tail[1:]
|
||||||
|
# data is stored with the input so ignore it when re-adding it
|
||||||
|
if len(tail) > 1 and tail[1] in (string.punctuation + ' '):
|
||||||
|
tail = _tail + new
|
||||||
|
else:
|
||||||
|
tail = _tail + ' ' + new
|
||||||
|
|
||||||
|
if len(tail) > 2 and tail[0] == 's' and tail[1] in string.punctuation:
|
||||||
|
if _tail == '':
|
||||||
|
return "I don't know about that."
|
||||||
|
args = tail.split(tail[1])
|
||||||
|
if len(args) == 4 and args[3] == '':
|
||||||
|
args = args[:-1]
|
||||||
|
if len(args) == 3:
|
||||||
|
replacement = True
|
||||||
|
_, src, dst = args
|
||||||
|
new_data = _tail.replace(src, dst, 1)
|
||||||
|
if new_data == _tail:
|
||||||
|
return 'replacement left data unchanged'
|
||||||
|
tail = new_data
|
||||||
|
else:
|
||||||
|
return 'invalid replacement syntax -- try s$foo$bar instead?'
|
||||||
|
|
||||||
|
db.execute("replace into memory(chan, word, data, nick) values"
|
||||||
|
" (?,lower(?),?,?)", (chan, head, head + ' ' + tail, nick))
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
if data:
|
||||||
|
if append:
|
||||||
|
return "appending %s to %s" % (new, data.replace('"', "''"))
|
||||||
|
elif replacement:
|
||||||
|
return "replacing '%s' with '%s' in %s" % (src, dst, _tail)
|
||||||
|
else:
|
||||||
|
return 'forgetting "%s", remembering this instead.' % \
|
||||||
|
data.replace('"', "''")
|
||||||
|
else:
|
||||||
|
return 'done.'
|
||||||
|
|
||||||
|
|
||||||
|
@hook.command
|
||||||
|
@hook.command("f")
|
||||||
|
def forget(inp, chan='', db=None):
|
||||||
|
".forget <word> -- forgets the mapping that word had"
|
||||||
|
|
||||||
|
db_init(db)
|
||||||
|
data = get_memory(db, chan, inp)
|
||||||
|
|
||||||
|
if data:
|
||||||
|
db.execute("delete from memory where chan=? and word=lower(?)",
|
||||||
|
(chan, inp))
|
||||||
|
db.commit()
|
||||||
|
return 'forgot `%s`' % data.replace('`', "'")
|
||||||
|
else:
|
||||||
|
return "I don't know about that."
|
||||||
|
|
||||||
|
|
||||||
|
@hook.regex(r'^\? ?(.+)')
|
||||||
|
def question(inp, chan='', say=None, db=None):
|
||||||
|
"?<word> -- shows what data is associated with word"
|
||||||
|
db_init(db)
|
||||||
|
|
||||||
|
data = get_memory(db, chan, inp.group(1).strip())
|
||||||
|
if data:
|
||||||
|
say(data)
|
||||||
|
|
||||||
|
|
||||||
|
class MemoryTest(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
import sqlite3
|
||||||
|
self.db = sqlite3.connect(':memory:')
|
||||||
|
|
||||||
|
def remember(self, inp, nick='someone', chan='#test'):
|
||||||
|
return remember(inp, nick=nick, chan=chan, db=self.db)
|
||||||
|
|
||||||
|
def forget(self, inp, chan='#test'):
|
||||||
|
return forget(inp, chan=chan, db=self.db)
|
||||||
|
|
||||||
|
def question(self, inp, chan='#test'):
|
||||||
|
output = []
|
||||||
|
question(re.match(r'(.*)', inp),
|
||||||
|
chan=chan, say=output.append, db=self.db)
|
||||||
|
return output[0] if output else None
|
||||||
|
|
||||||
|
def test_remember(self):
|
||||||
|
assert 'done.' == self.remember('dogs :3')
|
||||||
|
assert 'dogs :3' == self.question('dogs')
|
||||||
|
|
||||||
|
def test_remember_doc(self):
|
||||||
|
assert '.remember <word>' in self.remember('bad_syntax')
|
||||||
|
|
||||||
|
def test_remember_overwrite(self):
|
||||||
|
self.remember('dogs :(')
|
||||||
|
assert 'forgetting "dogs :("' in self.remember('dogs :3')
|
||||||
|
assert 'dogs :3' == self.question('dogs')
|
||||||
|
|
||||||
|
def test_remember_hygiene(self):
|
||||||
|
self.remember('python good', chan='#python')
|
||||||
|
self.remember('python bad', chan='#ruby')
|
||||||
|
assert 'python good' == self.question('python', '#python')
|
||||||
|
assert 'python bad' == self.question('python', '#ruby')
|
||||||
|
|
||||||
|
def test_remember_append(self):
|
||||||
|
self.remember('ball big')
|
||||||
|
self.remember('ball +red')
|
||||||
|
assert 'ball big red' == self.question('ball')
|
||||||
|
|
||||||
|
def test_remember_append_punctuation(self):
|
||||||
|
self.remember('baby young')
|
||||||
|
self.remember('baby +, hungry')
|
||||||
|
assert 'baby young, hungry' == self.question('baby')
|
||||||
|
|
||||||
|
def test_remember_replace(self):
|
||||||
|
self.remember('person is very rich (rich!)')
|
||||||
|
self.remember('person s/rich/poor/')
|
||||||
|
assert 'person is very poor (rich!)' == self.question('person')
|
||||||
|
|
||||||
|
def test_remember_replace_invalid(self):
|
||||||
|
self.remember('fact bar')
|
||||||
|
assert 'invalid replacement' in self.remember('fact s/too/many/seps/!')
|
||||||
|
assert 'invalid replacement' in self.remember('fact s/toofew')
|
||||||
|
|
||||||
|
def test_remember_replace_ineffective(self):
|
||||||
|
self.remember('hay stack')
|
||||||
|
assert 'unchanged' in self.remember('hay s:needle:shiny needle')
|
||||||
|
|
||||||
|
def test_remember_replace_missing(self):
|
||||||
|
assert "I don't know about that" in self.remember('hay s/what/lol')
|
||||||
|
|
||||||
|
def test_question_empty(self):
|
||||||
|
assert self.question('not_in_db') is None
|
||||||
|
|
||||||
|
def test_forget(self):
|
||||||
|
self.remember('meat good', chan='#carnivore')
|
||||||
|
self.remember('meat bad', chan='#vegan')
|
||||||
|
assert 'forgot `meat good`' in self.forget('meat', chan='#carnivore')
|
||||||
|
assert 'meat bad' == self.question('meat', chan='#vegan')
|
||||||
|
|
||||||
|
def test_forget_missing(self):
|
||||||
|
assert "don't know" in self.forget('fakekey')
|
|
@ -0,0 +1,51 @@
|
||||||
|
from random import choice, random, randint
|
||||||
|
import time
|
||||||
|
|
||||||
|
from util import hook
|
||||||
|
|
||||||
|
COLORS = ['03','04','06','07','08','09','10','11','12','13']
|
||||||
|
|
||||||
|
#Shibe generation code borrowed from aji
|
||||||
|
|
||||||
|
class pvec:
|
||||||
|
def __init__(self, num):
|
||||||
|
self.v = [1.0] * num
|
||||||
|
self.norm()
|
||||||
|
def norm(self):
|
||||||
|
s = sum(self.v)
|
||||||
|
self.v = [x / s for x in self.v]
|
||||||
|
def pick(self):
|
||||||
|
r = random() * sum(self.v) # sum should always be 1, but meh
|
||||||
|
s = 0
|
||||||
|
for i, x in enumerate(self.v):
|
||||||
|
s += x
|
||||||
|
if r < s:
|
||||||
|
break
|
||||||
|
def calc(j, x):
|
||||||
|
fac = (1 - 3.5 / (abs(i - j) + 4.5))
|
||||||
|
return x * fac
|
||||||
|
self.v = [calc(j, x) for j, x in enumerate(self.v)]
|
||||||
|
self.norm()
|
||||||
|
return i
|
||||||
|
|
||||||
|
spvec = pvec(40)
|
||||||
|
for i in range(10):
|
||||||
|
spvec.pick()
|
||||||
|
|
||||||
|
last_color = '00'
|
||||||
|
|
||||||
|
def gen_prefix():
|
||||||
|
global last_color
|
||||||
|
|
||||||
|
color = choice(COLORS)
|
||||||
|
while color == last_color:
|
||||||
|
color = choice(COLORS)
|
||||||
|
|
||||||
|
last_color = color
|
||||||
|
return ' ' * spvec.pick() + '\3' + color
|
||||||
|
|
||||||
|
@hook.command("shibe")
|
||||||
|
def shibeify(inp):
|
||||||
|
return "%s%s" %\
|
||||||
|
(gen_prefix(), inp)
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
from util import hook
|
||||||
|
|
||||||
|
@hook.regex("thanks mr skeltal")
|
||||||
|
def skeltal(_):
|
||||||
|
return "https://www.youtube.com/watch?v=10pqeNBg5d0"
|
||||||
|
|
||||||
|
@hook.regex(r"^([hH])([?!]*)$")
|
||||||
|
def h(inp, channel=None, conn=None):
|
||||||
|
suff = ""
|
||||||
|
if inp.group(2).startswith("?"):
|
||||||
|
suff = inp.group(2).replace("?", "!")
|
||||||
|
elif inp.group(2).startswith("!"):
|
||||||
|
suff = inp.group(2).replace("!", "?")
|
||||||
|
return inp.group(1) + suff
|
||||||
|
|
||||||
|
@hook.regex("dQw4w9WgXcQ")
|
||||||
|
def rickrollProtector(inp):
|
||||||
|
return "linked a rick roll, watch out"
|
||||||
|
|
||||||
|
@hook.regex("[kK]-[lL]ine")
|
||||||
|
def kline(inp):
|
||||||
|
return "http://i.imgur.com/FQjQgyB.jpg"
|
||||||
|
|
||||||
|
@hook.command
|
||||||
|
def botsnack(inp):
|
||||||
|
return ":D"
|
||||||
|
|
||||||
|
@hook.command
|
||||||
|
def debian_init(inp):
|
||||||
|
return "part 1: http://aceattorney.sparklin.org/player.php?trial_id=57684"
|
|
@ -0,0 +1,10 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
from util import hook
|
||||||
|
|
||||||
|
@hook.command
|
||||||
|
def source(inp):
|
||||||
|
if inp == "pull":
|
||||||
|
os.system("git pull")
|
||||||
|
return "updating..."
|
||||||
|
return "my source code: https://git.xeserv.us/xena/h"
|
|
@ -0,0 +1,29 @@
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
|
||||||
|
from util import hook, http
|
||||||
|
|
||||||
|
|
||||||
|
@hook.command
|
||||||
|
def suggest(inp, inp_unstripped=None):
|
||||||
|
".suggest [#n] <phrase> -- gets a random/the nth suggested google search"
|
||||||
|
|
||||||
|
if inp_unstripped is not None:
|
||||||
|
inp = inp_unstripped
|
||||||
|
m = re.match('^#(\d+) (.+)$', inp)
|
||||||
|
num = 0
|
||||||
|
if m:
|
||||||
|
num, inp = m.groups()
|
||||||
|
num = int(num)
|
||||||
|
|
||||||
|
json = http.get_json('http://suggestqueries.google.com/complete/search', client='firefox', q=inp)
|
||||||
|
suggestions = json[1]
|
||||||
|
if not suggestions:
|
||||||
|
return 'no suggestions found'
|
||||||
|
|
||||||
|
if not num:
|
||||||
|
num = random.randint(1, len(suggestions))
|
||||||
|
if len(suggestions) + 1 <= num:
|
||||||
|
return 'only got %d suggestions' % len(suggestions)
|
||||||
|
out = suggestions[num - 1]
|
||||||
|
return '#%d: %s' % (num, out)
|
|
@ -0,0 +1,9 @@
|
||||||
|
from util import hook, http
|
||||||
|
|
||||||
|
|
||||||
|
@hook.regex(r'(?i)http://(?:www\.)?tinyurl.com/([A-Za-z0-9\-]+)')
|
||||||
|
def tinyurl(match):
|
||||||
|
try:
|
||||||
|
return http.open(match.group()).url.strip()
|
||||||
|
except http.URLError, e:
|
||||||
|
pass
|
|
@ -0,0 +1,28 @@
|
||||||
|
names = "dozmarbinwansamlitsighidfidlissogdirwacsabwissibrigsoldopmodfoglidhopdardorlorhodfolrintogsilmirholpaslacrovlivdalsatlibtabhanticpidtorbolfosdotlosdilforpilramtirwintadbicdifrocwidbisdasmidloprilnardapmolsanlocnovsitnidtipsicropwitnatpanminritpodmottamtolsavposnapnopsomfinfonbanporworsipronnorbotwicsocwatdolmagpicdavbidbaltimtasmalligsivtagpadsaldivdactansidfabtarmonranniswolmispallasdismaprabtobrollatlonnodnavfignomnibpagsopralbilhaddocridmocpacravripfaltodtiltinhapmicfanpattaclabmogsimsonpinlomrictapfirhasbosbatpochactidhavsaplindibhosdabbitbarracparloddosbortochilmactomdigfilfasmithobharmighinradmashalraglagfadtopmophabnilnosmilfopfamdatnoldinhatnacrisfotribhocnimlarfitwalrapsarnalmoslandondanladdovrivbacpollaptalpitnambonrostonfodponsovnocsorlavmatmipfap"
|
||||||
|
endings = "zodnecbudwessevpersutletfulpensytdurwepserwylsunrypsyxdyrnuphebpeglupdepdysputlughecryttyvsydnexlunmeplutseppesdelsulpedtemledtulmetwenbynhexfebpyldulhetmevruttylwydtepbesdexsefwycburderneppurrysrebdennutsubpetrulsynregtydsupsemwynrecmegnetsecmulnymtevwebsummutnyxrextebfushepbenmuswyxsymselrucdecwexsyrwetdylmynmesdetbetbeltuxtugmyrpelsyptermebsetdutdegtexsurfeltudnuxruxrenwytnubmedlytdusnebrumtynseglyxpunresredfunrevrefmectedrusbexlebduxrynnumpyxrygryxfeptyrtustyclegnemfermertenlusnussyltecmexpubrymtucfyllepdebbermughuttunbylsudpemdevlurdefbusbeprunmelpexdytbyttyplevmylwedducfurfexnulluclennerlexrupnedlecrydlydfenwelnydhusrelrudneshesfetdesretdunlernyrsebhulrylludremlysfynwerrycsugnysnyllyndyndemluxfedsedbecmunlyrtesmudnytbyrsenwegfyrmurtelreptegpecnelnevfes"
|
||||||
|
|
||||||
|
def split_len(seq, length):
|
||||||
|
return [seq[i:i+length] for i in range(0, len(seq), length)]
|
||||||
|
|
||||||
|
prefix = split_len(names, 3)
|
||||||
|
suffix = split_len(endings, 3)
|
||||||
|
|
||||||
|
def ipv4tourbit(ip):
|
||||||
|
ip = map(lambda x: int(x), ip.split("."))
|
||||||
|
return "~%s%s-%s%s" % (prefix[ip[0]], suffix[ip[1]], prefix[ip[2]], suffix[ip[3]])
|
||||||
|
|
||||||
|
|
||||||
|
from util import hook
|
||||||
|
import random
|
||||||
|
|
||||||
|
@hook.command
|
||||||
|
def urbit(name):
|
||||||
|
random.seed(name if len(name) > 0 else None)
|
||||||
|
ip = "%d.%d.%d.%d" % (
|
||||||
|
random.randint(0,255),
|
||||||
|
random.randint(0,255),
|
||||||
|
random.randint(0,255),
|
||||||
|
random.randint(0,255),
|
||||||
|
)
|
||||||
|
|
||||||
|
return ipv4tourbit(ip)
|
|
@ -0,0 +1,80 @@
|
||||||
|
import math
|
||||||
|
import time
|
||||||
|
|
||||||
|
from util import hook, urlnorm, timesince
|
||||||
|
|
||||||
|
|
||||||
|
expiration_period = 60 * 60 * 24 # 1 day
|
||||||
|
|
||||||
|
ignored_urls = [urlnorm.normalize("http://google.com")]
|
||||||
|
|
||||||
|
|
||||||
|
def db_init(db):
|
||||||
|
db.execute("create table if not exists urlhistory"
|
||||||
|
"(chan, url, nick, time)")
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def insert_history(db, chan, url, nick):
|
||||||
|
db.execute("insert into urlhistory(chan, url, nick, time) "
|
||||||
|
"values(?,?,?,?)", (chan, url, nick, time.time()))
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def get_history(db, chan, url):
|
||||||
|
db.execute("delete from urlhistory where time < ?",
|
||||||
|
(time.time() - expiration_period,))
|
||||||
|
return db.execute("select nick, time from urlhistory where "
|
||||||
|
"chan=? and url=? order by time desc", (chan, url)).fetchall()
|
||||||
|
|
||||||
|
|
||||||
|
def nicklist(nicks):
|
||||||
|
nicks = sorted(dict(nicks), key=unicode.lower)
|
||||||
|
if len(nicks) <= 2:
|
||||||
|
return ' and '.join(nicks)
|
||||||
|
else:
|
||||||
|
return ', and '.join((', '.join(nicks[:-1]), nicks[-1]))
|
||||||
|
|
||||||
|
|
||||||
|
def format_reply(history):
|
||||||
|
if not history:
|
||||||
|
return
|
||||||
|
|
||||||
|
last_nick, recent_time = history[0]
|
||||||
|
last_time = timesince.timesince(recent_time)
|
||||||
|
|
||||||
|
if len(history) == 1:
|
||||||
|
return "%s linked that %s ago." % (last_nick, last_time)
|
||||||
|
|
||||||
|
hour_span = math.ceil((time.time() - history[-1][1]) / 3600)
|
||||||
|
hour_span = '%.0f hours' % hour_span if hour_span > 1 else 'hour'
|
||||||
|
|
||||||
|
hlen = len(history)
|
||||||
|
ordinal = ["once", "twice", "%d times" % hlen][min(hlen, 3) - 1]
|
||||||
|
|
||||||
|
if len(dict(history)) == 1:
|
||||||
|
last = "last linked %s ago" % last_time
|
||||||
|
else:
|
||||||
|
last = "last linked by %s %s ago" % (last_nick, last_time)
|
||||||
|
|
||||||
|
return "that url has been posted %s in the past %s by %s (%s)." % (ordinal,
|
||||||
|
hour_span, nicklist(history), last)
|
||||||
|
|
||||||
|
|
||||||
|
@hook.regex(r'([a-zA-Z]+://|www\.)[^ ]+')
|
||||||
|
def urlinput(match, nick='', chan='', db=None, bot=None):
|
||||||
|
db_init(db)
|
||||||
|
url = urlnorm.normalize(match.group().encode('utf-8'))
|
||||||
|
if url not in ignored_urls:
|
||||||
|
url = url.decode('utf-8')
|
||||||
|
history = get_history(db, chan, url)
|
||||||
|
insert_history(db, chan, url, nick)
|
||||||
|
|
||||||
|
inp = match.string.lower()
|
||||||
|
|
||||||
|
for name in dict(history):
|
||||||
|
if name.lower() in inp: # person was probably quoting a line
|
||||||
|
return # that had a link. don't remind them.
|
||||||
|
|
||||||
|
if nick not in dict(history):
|
||||||
|
return format_reply(history)
|
|
@ -0,0 +1,108 @@
|
||||||
|
import inspect
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def _hook_add(func, add, name=''):
|
||||||
|
if not hasattr(func, '_hook'):
|
||||||
|
func._hook = []
|
||||||
|
func._hook.append(add)
|
||||||
|
|
||||||
|
if not hasattr(func, '_filename'):
|
||||||
|
func._filename = func.func_code.co_filename
|
||||||
|
|
||||||
|
if not hasattr(func, '_args'):
|
||||||
|
argspec = inspect.getargspec(func)
|
||||||
|
if name:
|
||||||
|
n_args = len(argspec.args)
|
||||||
|
if argspec.defaults:
|
||||||
|
n_args -= len(argspec.defaults)
|
||||||
|
if argspec.keywords:
|
||||||
|
n_args -= 1
|
||||||
|
if argspec.varargs:
|
||||||
|
n_args -= 1
|
||||||
|
if n_args != 1:
|
||||||
|
err = '%ss must take 1 non-keyword argument (%s)' % (name,
|
||||||
|
func.__name__)
|
||||||
|
raise ValueError(err)
|
||||||
|
|
||||||
|
args = []
|
||||||
|
if argspec.defaults:
|
||||||
|
end = bool(argspec.keywords) + bool(argspec.varargs)
|
||||||
|
args.extend(argspec.args[-len(argspec.defaults):
|
||||||
|
end if end else None])
|
||||||
|
if argspec.keywords:
|
||||||
|
args.append(0) # means kwargs present
|
||||||
|
func._args = args
|
||||||
|
|
||||||
|
if not hasattr(func, '_thread'): # does function run in its own thread?
|
||||||
|
func._thread = False
|
||||||
|
|
||||||
|
|
||||||
|
def sieve(func):
|
||||||
|
if func.func_code.co_argcount != 5:
|
||||||
|
raise ValueError(
|
||||||
|
'sieves must take 5 arguments: (bot, input, func, type, args)')
|
||||||
|
_hook_add(func, ['sieve', (func,)])
|
||||||
|
return func
|
||||||
|
|
||||||
|
|
||||||
|
def command(arg=None, **kwargs):
|
||||||
|
args = {}
|
||||||
|
|
||||||
|
def command_wrapper(func):
|
||||||
|
args.setdefault('name', func.func_name)
|
||||||
|
_hook_add(func, ['command', (func, args)], 'command')
|
||||||
|
return func
|
||||||
|
|
||||||
|
if kwargs or not inspect.isfunction(arg):
|
||||||
|
if arg is not None:
|
||||||
|
args['name'] = arg
|
||||||
|
args.update(kwargs)
|
||||||
|
return command_wrapper
|
||||||
|
else:
|
||||||
|
return command_wrapper(arg)
|
||||||
|
|
||||||
|
|
||||||
|
def event(arg=None, **kwargs):
|
||||||
|
args = kwargs
|
||||||
|
|
||||||
|
def event_wrapper(func):
|
||||||
|
args['name'] = func.func_name
|
||||||
|
args.setdefault('events', ['*'])
|
||||||
|
_hook_add(func, ['event', (func, args)], 'event')
|
||||||
|
return func
|
||||||
|
|
||||||
|
if inspect.isfunction(arg):
|
||||||
|
return event_wrapper(arg, kwargs)
|
||||||
|
else:
|
||||||
|
if arg is not None:
|
||||||
|
args['events'] = arg.split()
|
||||||
|
return event_wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def singlethread(func):
|
||||||
|
func._thread = True
|
||||||
|
return func
|
||||||
|
|
||||||
|
|
||||||
|
def api_key(key):
|
||||||
|
def annotate(func):
|
||||||
|
func._apikey = key
|
||||||
|
return func
|
||||||
|
return annotate
|
||||||
|
|
||||||
|
|
||||||
|
def regex(regex, flags=0, **kwargs):
|
||||||
|
args = kwargs
|
||||||
|
|
||||||
|
def regex_wrapper(func):
|
||||||
|
args['name'] = func.func_name
|
||||||
|
args['regex'] = regex
|
||||||
|
args['re'] = re.compile(regex, flags)
|
||||||
|
_hook_add(func, ['regex', (func, args)], 'regex')
|
||||||
|
return func
|
||||||
|
|
||||||
|
if inspect.isfunction(regex):
|
||||||
|
raise ValueError("regex decorators require a regex to match against")
|
||||||
|
else:
|
||||||
|
return regex_wrapper
|
|
@ -0,0 +1,173 @@
|
||||||
|
# convenience wrapper for urllib2 & friends
|
||||||
|
import binascii
|
||||||
|
import cookielib
|
||||||
|
import hmac
|
||||||
|
import json
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
import time
|
||||||
|
import urllib
|
||||||
|
import urllib2
|
||||||
|
import urlparse
|
||||||
|
|
||||||
|
from hashlib import sha1
|
||||||
|
from urllib import quote, unquote, quote_plus as _quote_plus
|
||||||
|
from urllib2 import HTTPError, URLError
|
||||||
|
|
||||||
|
from lxml import etree, html
|
||||||
|
|
||||||
|
|
||||||
|
ua_skybot = 'Skybot/1.0 http://github.com/rmmh/skybot'
|
||||||
|
|
||||||
|
ua_firefox = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.6) ' \
|
||||||
|
'Gecko/20070725 Firefox/2.0.0.6'
|
||||||
|
ua_internetexplorer = 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)'
|
||||||
|
|
||||||
|
jar = cookielib.CookieJar()
|
||||||
|
|
||||||
|
|
||||||
|
def get(*args, **kwargs):
|
||||||
|
return open(*args, **kwargs).read()
|
||||||
|
|
||||||
|
|
||||||
|
def get_html(*args, **kwargs):
|
||||||
|
return html.fromstring(get(*args, **kwargs))
|
||||||
|
|
||||||
|
|
||||||
|
def get_xml(*args, **kwargs):
|
||||||
|
return etree.fromstring(get(*args, **kwargs))
|
||||||
|
|
||||||
|
|
||||||
|
def get_json(*args, **kwargs):
|
||||||
|
return json.loads(get(*args, **kwargs))
|
||||||
|
|
||||||
|
|
||||||
|
def open(url, query_params=None, post_data=None,
|
||||||
|
get_method=None, cookies=False, oauth=False, oauth_keys=None, headers=None, **kwargs):
|
||||||
|
if query_params is None:
|
||||||
|
query_params = {}
|
||||||
|
|
||||||
|
query_params.update(kwargs)
|
||||||
|
|
||||||
|
url = prepare_url(url, query_params)
|
||||||
|
|
||||||
|
request = urllib2.Request(url, post_data)
|
||||||
|
|
||||||
|
if get_method is not None:
|
||||||
|
request.get_method = lambda: get_method
|
||||||
|
|
||||||
|
if headers is not None:
|
||||||
|
for header_key, header_value in headers.iteritems():
|
||||||
|
request.add_header(header_key, header_value)
|
||||||
|
|
||||||
|
if 'User-Agent' not in request.headers:
|
||||||
|
request.add_header('User-Agent', ua_skybot)
|
||||||
|
|
||||||
|
if oauth:
|
||||||
|
nonce = oauth_nonce()
|
||||||
|
timestamp = oauth_timestamp()
|
||||||
|
api_url, req_data = string.split(url, "?")
|
||||||
|
unsigned_request = oauth_unsigned_request(
|
||||||
|
nonce, timestamp, req_data, oauth_keys['consumer'], oauth_keys['access'])
|
||||||
|
|
||||||
|
signature = oauth_sign_request("GET", api_url, req_data, unsigned_request, oauth_keys[
|
||||||
|
'consumer_secret'], oauth_keys['access_secret'])
|
||||||
|
|
||||||
|
header = oauth_build_header(
|
||||||
|
nonce, signature, timestamp, oauth_keys['consumer'], oauth_keys['access'])
|
||||||
|
request.add_header('Authorization', header)
|
||||||
|
|
||||||
|
if cookies:
|
||||||
|
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(jar))
|
||||||
|
else:
|
||||||
|
opener = urllib2.build_opener()
|
||||||
|
return opener.open(request)
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_url(url, queries):
|
||||||
|
if queries:
|
||||||
|
scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
|
||||||
|
|
||||||
|
query = dict(urlparse.parse_qsl(query))
|
||||||
|
query.update(queries)
|
||||||
|
query = urllib.urlencode(dict((to_utf8(key), to_utf8(value))
|
||||||
|
for key, value in query.iteritems()))
|
||||||
|
|
||||||
|
url = urlparse.urlunsplit((scheme, netloc, path, query, fragment))
|
||||||
|
|
||||||
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
def to_utf8(s):
|
||||||
|
if isinstance(s, unicode):
|
||||||
|
return s.encode('utf8', 'ignore')
|
||||||
|
else:
|
||||||
|
return str(s)
|
||||||
|
|
||||||
|
|
||||||
|
def quote_plus(s):
|
||||||
|
return _quote_plus(to_utf8(s))
|
||||||
|
|
||||||
|
|
||||||
|
def oauth_nonce():
|
||||||
|
return ''.join([str(random.randint(0, 9)) for i in range(8)])
|
||||||
|
|
||||||
|
|
||||||
|
def oauth_timestamp():
|
||||||
|
return str(int(time.time()))
|
||||||
|
|
||||||
|
|
||||||
|
def oauth_unsigned_request(nonce, timestamp, req, consumer, token):
|
||||||
|
d = {'oauth_consumer_key': consumer,
|
||||||
|
'oauth_nonce': nonce,
|
||||||
|
'oauth_signature_method': 'HMAC-SHA1',
|
||||||
|
'oauth_timestamp': timestamp,
|
||||||
|
'oauth_token': token,
|
||||||
|
'oauth_version': '1.0'}
|
||||||
|
|
||||||
|
k, v = string.split(req, "=")
|
||||||
|
d[k] = v
|
||||||
|
|
||||||
|
unsigned_req = ''
|
||||||
|
|
||||||
|
for x in sorted(d, key=lambda key: key):
|
||||||
|
unsigned_req += x + "=" + d[x] + "&"
|
||||||
|
|
||||||
|
unsigned_req = quote(unsigned_req[:-1])
|
||||||
|
|
||||||
|
return unsigned_req
|
||||||
|
|
||||||
|
|
||||||
|
def oauth_build_header(nonce, signature, timestamp, consumer, token):
|
||||||
|
d = {'oauth_consumer_key': consumer,
|
||||||
|
'oauth_nonce': nonce,
|
||||||
|
'oauth_signature': signature,
|
||||||
|
'oauth_signature_method': 'HMAC-SHA1',
|
||||||
|
'oauth_timestamp': timestamp,
|
||||||
|
'oauth_token': token,
|
||||||
|
'oauth_version': '1.0'}
|
||||||
|
|
||||||
|
header = 'OAuth '
|
||||||
|
|
||||||
|
for x in sorted(d, key=lambda key: key):
|
||||||
|
header += x + '="' + d[x] + '", '
|
||||||
|
|
||||||
|
return header[:-1]
|
||||||
|
|
||||||
|
|
||||||
|
def oauth_sign_request(method, url, params, unsigned_request, consumer_secret, token_secret):
|
||||||
|
key = consumer_secret + "&" + token_secret
|
||||||
|
|
||||||
|
base = method + "&" + quote(url, '') + "&" + unsigned_request
|
||||||
|
|
||||||
|
hash = hmac.new(key, base, sha1)
|
||||||
|
|
||||||
|
signature = quote(binascii.b2a_base64(hash.digest())[:-1])
|
||||||
|
|
||||||
|
return signature
|
||||||
|
|
||||||
|
|
||||||
|
def unescape(s):
|
||||||
|
if not s.strip():
|
||||||
|
return s
|
||||||
|
return html.fromstring(s).text_content()
|
|
@ -0,0 +1,102 @@
|
||||||
|
# Copyright (c) Django Software Foundation and individual contributors.
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# 3. Neither the name of Django nor the names of its contributors may be used
|
||||||
|
# to endorse or promote products derived from this software without
|
||||||
|
# specific prior written permission.
|
||||||
|
#
|
||||||
|
#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"AND
|
||||||
|
#ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
#WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
#DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
#ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
#(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
#LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
#ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
#(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
#SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
def timesince(d, now=None):
|
||||||
|
"""
|
||||||
|
Takes two datetime objects and returns the time between d and now
|
||||||
|
as a nicely formatted string, e.g. "10 minutes". If d occurs after now,
|
||||||
|
then "0 minutes" is returned.
|
||||||
|
|
||||||
|
Units used are years, months, weeks, days, hours, and minutes.
|
||||||
|
Seconds and microseconds are ignored. Up to two adjacent units will be
|
||||||
|
displayed. For example, "2 weeks, 3 days" and "1 year, 3 months" are
|
||||||
|
possible outputs, but "2 weeks, 3 hours" and "1 year, 5 days" are not.
|
||||||
|
|
||||||
|
Adapted from http://blog.natbat.co.uk/archive/2003/Jun/14/time_since
|
||||||
|
"""
|
||||||
|
chunks = (
|
||||||
|
(60 * 60 * 24 * 365, ('year', 'years')),
|
||||||
|
(60 * 60 * 24 * 30, ('month', 'months')),
|
||||||
|
(60 * 60 * 24 * 7, ('week', 'weeks')),
|
||||||
|
(60 * 60 * 24, ('day', 'days')),
|
||||||
|
(60 * 60, ('hour', 'hours')),
|
||||||
|
(60, ('minute', 'minutes'))
|
||||||
|
)
|
||||||
|
|
||||||
|
# Convert int or float (unix epoch) to datetime.datetime for comparison
|
||||||
|
if isinstance(d, int) or isinstance(d, float):
|
||||||
|
d = datetime.datetime.fromtimestamp(d)
|
||||||
|
|
||||||
|
# Convert datetime.date to datetime.datetime for comparison.
|
||||||
|
if not isinstance(d, datetime.datetime):
|
||||||
|
d = datetime.datetime(d.year, d.month, d.day)
|
||||||
|
if now and not isinstance(now, datetime.datetime):
|
||||||
|
now = datetime.datetime(now.year, now.month, now.day)
|
||||||
|
|
||||||
|
if not now:
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
|
||||||
|
# ignore microsecond part of 'd' since we removed it from 'now'
|
||||||
|
delta = now - (d - datetime.timedelta(0, 0, d.microsecond))
|
||||||
|
since = delta.days * 24 * 60 * 60 + delta.seconds
|
||||||
|
if since <= 0:
|
||||||
|
# d is in the future compared to now, stop processing.
|
||||||
|
return u'0 ' + 'minutes'
|
||||||
|
for i, (seconds, name) in enumerate(chunks):
|
||||||
|
count = since // seconds
|
||||||
|
if count != 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
if count == 1:
|
||||||
|
s = '%(number)d %(type)s' % {'number': count, 'type': name[0]}
|
||||||
|
else:
|
||||||
|
s = '%(number)d %(type)s' % {'number': count, 'type': name[1]}
|
||||||
|
|
||||||
|
if i + 1 < len(chunks):
|
||||||
|
# Now get the second item
|
||||||
|
seconds2, name2 = chunks[i + 1]
|
||||||
|
count2 = (since - (seconds * count)) // seconds2
|
||||||
|
if count2 != 0:
|
||||||
|
if count2 == 1:
|
||||||
|
s += ', %d %s' % (count2, name2[0])
|
||||||
|
else:
|
||||||
|
s += ', %d %s' % (count2, name2[1])
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def timeuntil(d, now=None):
|
||||||
|
"""
|
||||||
|
Like timesince, but returns a string measuring the time until
|
||||||
|
the given time.
|
||||||
|
"""
|
||||||
|
if not now:
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
return timesince(now, d)
|
|
@ -0,0 +1,133 @@
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
import unicodedata
|
||||||
|
import urlparse
|
||||||
|
from urllib import quote, unquote
|
||||||
|
|
||||||
|
default_port = {
|
||||||
|
'http': 80,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Normalizer(object):
|
||||||
|
def __init__(self, regex, normalize_func):
|
||||||
|
self.regex = regex
|
||||||
|
self.normalize = normalize_func
|
||||||
|
|
||||||
|
normalizers = ( Normalizer( re.compile(r'(?:https?://)?(?:[a-zA-Z0-9\-]+\.)?(?:amazon|amzn){1}\.(?P<tld>[a-zA-Z\.]{2,})\/(gp/(?:product|offer-listing|customer-media/product-gallery)/|exec/obidos/tg/detail/-/|o/ASIN/|dp/|(?:[A-Za-z0-9\-]+)/dp/)?(?P<ASIN>[0-9A-Za-z]{10})'),
|
||||||
|
lambda m: r'http://amazon.%s/dp/%s' % (m.group('tld'), m.group('ASIN'))),
|
||||||
|
Normalizer( re.compile(r'.*waffleimages\.com.*/([0-9a-fA-F]{40})'),
|
||||||
|
lambda m: r'http://img.waffleimages.com/%s' % m.group(1) ),
|
||||||
|
Normalizer( re.compile(r'(?:youtube.*?(?:v=|/v/)|youtu\.be/|yooouuutuuube.*?id=)([-_a-z0-9]+)'),
|
||||||
|
lambda m: r'http://youtube.com/watch?v=%s' % m.group(1) ),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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 += "#"
|
||||||
|
normal_url = urlparse.urlunsplit((scheme, auth, path, query,
|
||||||
|
fragment)).replace("http:///", "http://")
|
||||||
|
for norm in normalizers:
|
||||||
|
m = norm.regex.match(normal_url)
|
||||||
|
if m:
|
||||||
|
return norm.normalize(m)
|
||||||
|
return normal_url
|
|
@ -0,0 +1,21 @@
|
||||||
|
from util import hook
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
@hook.command
|
||||||
|
def minecraft(pls):
|
||||||
|
r = requests.get("http://xeserv.us/api/minecraft.json")
|
||||||
|
|
||||||
|
data = r.json()
|
||||||
|
|
||||||
|
if not data["online"]:
|
||||||
|
return "fluttershy.yochat.biz is down, oh noes"
|
||||||
|
|
||||||
|
if data["players"] == None:
|
||||||
|
return "fluttershy.yochat.biz has no online players"
|
||||||
|
|
||||||
|
return "fluttershy.yochat.biz has the following players: " + " ".join(data["players"])
|
||||||
|
|
||||||
|
@hook.command
|
||||||
|
def tf2(pls):
|
||||||
|
pass
|
|
@ -0,0 +1,10 @@
|
||||||
|
PyYAML==3.11
|
||||||
|
argparse==1.2.1
|
||||||
|
dateandtime==0.0.5
|
||||||
|
ddate==0.1.0
|
||||||
|
fuckit==4.8.0
|
||||||
|
hask==0.0.1
|
||||||
|
lxml==3.5.0
|
||||||
|
matrix-client==0.0.2
|
||||||
|
requests==2.8.1
|
||||||
|
wsgiref==0.1.2
|
Loading…
Reference in New Issue