initial commit

This commit is contained in:
Christine Dodrill 2015-11-28 22:42:25 -08:00
commit 33f801bc5e
39 changed files with 6308 additions and 0 deletions

12
.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
.*.swp
*.pyc
*.orig
persist
config
pep8.py
.project
.pydevproject
*.db
web
env
config.yml

7
LICENSE Normal file
View File

@ -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.

35
README.md Normal file
View File

@ -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

14
core/db.py Normal file
View File

@ -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

199
core/main.py Normal file
View File

@ -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)

161
core/reload.py Normal file
View File

@ -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

7
env.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
virtualenv env
. ./env/bin/activate
pip install git+https://github.com/billpmurphy/hask
pip install -r requirements.txt

67
main.py Normal file
View File

@ -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)

15
plugins/bobross.py Normal file
View File

@ -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)

9
plugins/compliment.py Normal file
View File

@ -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

166
plugins/data/bobross.json Normal file
View File

@ -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

16
plugins/data/plinkett.txt Normal file
View File

@ -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

8
plugins/ddate.py Normal file
View File

@ -0,0 +1,8 @@
from util import hook
from ddate.base import DDate
import datetime
@hook.command
def ddate(inp):
return str(DDate())

11
plugins/derpiback.py Normal file
View File

@ -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"

315
plugins/hack.py Normal file
View File

@ -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

103
plugins/log.py Normal file
View File

@ -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')

26
plugins/opname.py Normal file
View File

@ -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()

12
plugins/plinkett.py Normal file
View File

@ -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)

53
plugins/pony.py Normal file
View File

@ -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)

84
plugins/ponyapi.py Normal file
View File

@ -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"]

19
plugins/printerfact.py Normal file
View File

@ -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))

193
plugins/quote.py Normal file
View File

@ -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()

189
plugins/remember.py Normal file
View File

@ -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')

51
plugins/shibe.py Normal file
View File

@ -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)

30
plugins/shitposting.py Normal file
View File

@ -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"

10
plugins/source.py Normal file
View File

@ -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"

29
plugins/suggest.py Normal file
View File

@ -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)

9
plugins/tinyurl.py Normal file
View File

@ -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

28
plugins/urbit.py Normal file
View File

@ -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)

80
plugins/urlhistory.py Normal file
View File

@ -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
plugins/util/__init__.py Normal file
View File

108
plugins/util/hook.py Normal file
View File

@ -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

173
plugins/util/http.py Normal file
View File

@ -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()

102
plugins/util/timesince.py Normal file
View File

@ -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)

133
plugins/util/urlnorm.py Normal file
View File

@ -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

21
plugins/xeserv.py Normal file
View File

@ -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

10
requirements.txt Normal file
View File

@ -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