Skip to content

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also .

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also .
...
  • 17 commits
  • 13 files changed
  • 0 commit comments
  • 3 contributors
View
52 README.mkd
@@ -2,29 +2,67 @@ Hamper is an IRC bot to amuse us.
Installation
============
-As of right now, you can't install hamper, unless you are clever.
+You can install the latest official version of hamper from the [Python Package
+Index][pypi]. I suggest using `pip`, but I am told `easy_install` will work as
+well.
+
+ sudo pip install hamper
+
+If you want the git version of hamper, then checkout out the develop branch,
+and run
+
+ sudo python setup.py install
+
+[pypi]: http://pypi.python.org/pypi
+
+Dependencies
+------------
+These dependencies will be taken care of automatically if you install with
+`pip`. They are only a concern if you install from git.
+
+- Twisted
+- SQLAlchemy
+- PyYaml
+- The plugin loader of [Bravo][bravo] (included)
+- Exocet (included)
+
+[bravo]: https://github.com/MostAwesomeDude/bravo
Configuration
=============
-Make a file named `hamper.conf`. This should be a YAML file containing these,
-hopefully self-explanatory, fields:
+Make a file named `hamper.conf`. This should be a YAML file containing these
+fields:
- `nickname`
- `channel`
- `server`
- `port`
+- `db` - A database URL as described [here][dburl]
For an example check out `hamper.conf.dist`.
+[dburl]: http://www.sqlalchemy.org/docs/core/engines.html#sqlalchemy.create_engine
+
Usage
=====
-Then, with `hamper.conf` in your current directory and hamper on your python
-path, run `main.py`. I like to use this command
+Run hamper from a directory containing `hamper.conf`. If you installed it with
+pip, you can just say `hamper`, but if you are running from git, you need to
+make sure that hamper is on your python path. I like to use this command:
- PYTHONPATH="~/git/hamper" python2 ~/git/hamper/hamper/main.py
+ PYTHONPATH="~/git/hamper" python2 ~/git/hamper/hamper/scripts/hamper
+
+Plugin Development
+==================
+Read `hamper/plugins/friendly.py`. Add a file to `hamper/plugins`, and write
+plugins in it. Don't forget to create an instance of each one at the bottom.
###Credits
+Code and design:
+
+- Mike Cooper <mythmon@gmail.com>
+
+Ideas, but no code yet:
+
- Daniel Thornton <merthel@gmail.com>
- Jordan Evans <evans.jordan.m@gmail.com>
-- Mike Cooper <mythmon@gmail.com>
View
6 bravo/ibravo.py
@@ -1,5 +1,4 @@
from twisted.python.components import registerAdapter
-from twisted.web.resource import IResource
from zope.interface import implements, invariant, Attribute, Interface
from bravo.errors import InvariantException
@@ -449,8 +448,3 @@ def stop():
After this method is called, the automaton should not continue
processing data; it needs to stop immediately.
"""
-
-class IWorldResource(IBravoPlugin, IResource):
- """
- Interface for a world specific web resource.
- """
View
15 hamper.conf.dist
@@ -1,4 +1,15 @@
-nickname: hamper
-channel: #hamper
+nickname: cool_bot
server: irc.freenode.net
port: 6667
+db: "sqlite:///hamper.db"
+
+channels: ["#awesome-channel", "#cool-channel"]
+plugins:
+ - quit
+ - sed
+ - lmgtfy
+ - friendly
+ - ponies
+ - botsnack
+ - plugins
+
View
2 hamper/__init__.py
@@ -1 +1 @@
-version = '0.1'
+version = '0.2'
View
46 hamper/commander.py
@@ -1,8 +1,9 @@
import sys
import re
from collections import deque
-import yaml
+import traceback
+import yaml
from twisted.words.protocols import irc
from twisted.internet import protocol, reactor
import sqlalchemy
@@ -24,11 +25,12 @@ def _get_db(self):
db = property(_get_db)
def signedOn(self):
- self.join(self.factory.channel)
+ for c in self.factory.channels:
+ self.join(c)
print "Signed on as %s." % (self.nickname,)
def joined(self, channel):
- print "Joined %s." % (channel,)
+ print "Joined {0}.".format(channel)
def privmsg(self, user, channel, msg):
"""I received a message."""
@@ -58,9 +60,9 @@ def privmsg(self, user, channel, msg):
msg = msg[1:]
directed = True
- if user:
+ try:
user, mask = user.split('!', 1)
- else:
+ except:
mask = ''
comm = {
@@ -74,26 +76,14 @@ def privmsg(self, user, channel, msg):
}
# Plugins are already sorted by priority
+ stop = False
for plugin in self.factory.plugins:
- stop = plugin.process(self, comm)
- if stop:
- break
-
- #matchedPlugins = []
- #for cmd in self.factory.plugins:
- # match = cmd.regex.match(msg)
- # if match and (directed or (not cmd.onlyDirected)):
- # matchedPlugins.append((match, cmd))
-
- ## High priority plugins first
- #matchedPlugins.sort(key=lambda x: x[1].priority, reverse=True)
-
- #for match, cmd in matchedPlugins:
- # proc_comm = comm.copy()
- # proc_comm.update({'groups': match.groups()})
- # if not cmd(self, proc_comm):
- # # The plugin asked us to not run any more.
- # break
+ try:
+ stop = plugin.process(self, comm)
+ if stop:
+ break
+ except:
+ traceback.print_exc()
if not channel in self.factory.history:
self.factory.history[channel] = deque(maxlen=100)
@@ -114,9 +104,6 @@ def connectionLost(self, reason):
self.factory.db.commit()
reactor.stop()
- def say(self, msg):
- self.msg(self.factory.channel, msg)
-
def removePlugin(self, plugin):
self.factory.pluginsToRemove.append(plugin)
@@ -134,7 +121,7 @@ class CommanderFactory(protocol.ClientFactory):
protocol = CommanderProtocol
def __init__(self, config):
- self.channel = config['channel']
+ self.channels = config['channels']
self.nickname = config['nickname']
self.history = {}
@@ -155,7 +142,8 @@ def __init__(self, config):
self.db = DBSession()
for _, plugin in retrieve_plugins(IPlugin, 'hamper.plugins').items():
- self.registerPlugin(plugin)
+ if plugin.name in config['plugins']:
+ self.registerPlugin(plugin)
def clientConnectionLost(self, connector, reason):
print "Lost connection (%s)." % (reason)
View
58 hamper/interfaces.py
@@ -24,31 +24,71 @@ def process(bot, comm):
"""
-class Command(object):
- """Specialized plugin to implement a simple command"""
+class Plugin(object):
+ """
+ Base class for a plugin.
+
+ If any of a classes children are Command classes, automatically call out to
+ them.
+ """
implements(IPlugin)
priority = 0
+ def __init__(self):
+ self.commands = []
+ for name in dir(self):
+ cls = self.__getattribute__(name)
+ try:
+ if ICommand.implementedBy(cls):
+ self.commands.append(cls())
+ except (AttributeError, TypeError):
+ pass
+
+ def setup(self, factory):
+ pass
+
+ def process(self, bot, comm):
+ for cmd in self.commands:
+ stop = cmd.process(bot, comm)
+ if stop:
+ return stop
+
+
+class ICommand(Interface):
+ """Interface for a command."""
+
+ regex = Attribute('The regex to trigger this command for.')
+ caseSensitive = Attribute("The case sensitivity of the trigger regex.")
+ onlyDirected = Attribute("Only respond to command directed at the bot.")
+
+ def process(bot, comm):
+ """Chooses whether or not to trigger the command."""
+
+ def command(bot, comm, groups):
+ """This function gets called when the command is triggered."""
+
+
+class Command(object):
+ """
+ A convenience wrapper to implement a single command.
+
+ To use it, define a clas that inherits from Command inside a Plugin.
+ """
+ implements(ICommand)
+
caseSensitive = False
- regex = ''
onlyDirected = True
def __init__(self):
if type(self.regex) == str:
opts = 0 if self.caseSensitive else re.I
self.regex = re.compile(self.regex, opts)
- def setup(self, factory):
- pass
-
def process(self, bot, comm):
if self.onlyDirected and not comm['directed']:
return
match = self.regex.match(comm['message'])
if match:
self.command(bot, comm, match.groups())
return True
-
- def command(self, bot, comm, groups):
- pass
View
168 hamper/plugins/commands.py
@@ -3,73 +3,137 @@
from zope.interface import implements, Interface, Attribute
-from hamper.interfaces import Command
+from hamper.interfaces import Command, Plugin
-class QuitCommand(Command):
+class Quit(Plugin):
"""Know when hamper isn't wanted."""
-
name = 'quit'
- regex = 'go away'
- def command(self, bot, comm, groups):
- bot.say('Bye!')
- bot.leaveChannel(comm['channel'])
- return True
+ class QuitCommand(Command):
+ regex = 'go away'
+
+ def command(self, bot, comm, groups):
+ if comm['pm']:
+ bot.msg(comm['channel'], "You can't do that from PM.")
+ return False
+
+ bot.msg(comm['channel'], 'Bye!')
+ bot.leaveChannel(comm['channel'])
+ return True
-class Sed(Command):
+class Sed(Plugin):
"""To be honest, I feel strange in channels that don't have this."""
name = 'sed'
- regex = r's/(.*)/(.*)/(g?i?m?)'
- onlyDirected = False
priority = -1
- def command(self, bot, comm, groups):
- options = groups[2]
-
- regex_opts = re.I if 'i' in options else 0
- usr_regex = re.compile(groups[0], regex_opts)
- usr_replace = groups[1]
-
- count = 0 if 'g' in options else 1
-
- key = comm['channel']
- if key not in bot.factory.history:
- bot.say('Who are you?! How did you get in my house?!')
- return
-
- for hist in reversed(bot.factory.history[key]):
- # Only look at our own if only-me was specified
- if 'm' in options and hist['user'] != comm['user']:
- continue
- # Don't look at other sed commands
- if hist['directed'] and hist['message'].startswith('s/'):
- continue
-
- if usr_regex.search(hist['message']):
- new_msg = usr_regex.sub(usr_replace, hist['message'], count)
- bot.say('{0} actually meant: {1}'
- .format(hist['user'], new_msg))
- break
- else:
- bot.say("Sorry, I couldn't match /{0}/.".format(usr_regex.pattern))
-
-class LetMeGoogleThatForYou(Command):
+ class SedCommand(Command):
+ regex = r's/(.*)/(.*)/(g?i?m?)'
+ onlyDirected = False
+
+ def command(self, bot, comm, groups):
+ options = groups[2]
+
+ regex_opts = re.I if 'i' in options else 0
+ usr_regex = re.compile(groups[0], regex_opts)
+ usr_replace = groups[1]
+
+ g = 0 if 'g' in options else 1
+
+ key = comm['channel']
+ if key not in bot.factory.history:
+ bot.msg(comm['channel'], 'Who are you?! How did you get in my '
+ 'house?!')
+ return False
+
+ for hist in reversed(bot.factory.history[key]):
+ if 'm' in options and hist['user'] != comm['user']:
+ # Only look at the user's messages
+ continue
+
+ # Don't look at other sed commands
+ if hist['directed'] and hist['message'].startswith('s/'):
+ continue
+
+ if usr_regex.search(hist['message']):
+ new_msg = usr_regex.sub(usr_replace, hist['message'], g)
+ bot.msg(comm['channel'], '{0} actually meant: {1}'
+ .format(hist['user'], new_msg))
+ break
+ else:
+ bot.msg(comm['channel'],
+ "Sorry, I couldn't match /{0}/.".format(usr_regex.pattern))
+
+class LetMeGoogleThatForYou(Plugin):
"""Link to the sarcastic letmegooglethatforyou.com."""
name = 'lmgtfy'
- regex = '^lmgtfy\s+(.*)'
- onlyDirected = False
-
- def command(self, bot, comm, groups):
- target = ''
- if comm['target']:
- target = comm['target'] + ': '
- args = groups[0].replace(' ', '+')
- bot.say(target + 'http://lmgtfy.com/?q=' + args)
+ class LMGTFYCommand(Command):
+ regex = '^lmgtfy\s+(.*)'
+ onlyDirected = False
+
+ def command(self, bot, comm, groups):
+ target = ''
+ if comm['target']:
+ target = comm['target'] + ': '
+ args = groups[0].replace(' ', '+')
+ bot.msg(comm['channel'], target + 'http://lmgtfy.com/?q=' + args)
+
+def roll(num, sides, add):
+ """Rolls a die of sides sides, num times, sums them, and adds add"""
+ rolls = []
+ for i in range(num):
+ rolls.append(random.randint(1,sides))
+ rolls.append(add)
+ return rolls
+
+class Dice(Plugin):
+ """Random dice rolls!"""
+ name = 'dice'
+ onlyDirected = True
+ priority = 3
+ regex = '((\d*)d)?(\d*)(\+(\d*))?'
+
+ class DiceCommand(Command):
+ onlyDirected = True
+ regex = '((\d*)d)?(\d*)(\+(\d*))?'
+
+ def command(self, bot, com, groups):
+ _, num, sides,_, add = groups
+
+ if not num:
+ num = 1
+ else:
+ num = int(num)
+
+ if not sides:
+ sides = 6
+ else:
+ sides = int (sides)
+
+ if not add:
+ add = 0
+ else:
+ add = int(add)
+
+ result = roll(num,sides,add)
+ output = ""
+ output += "%s: You rolled %sd%s+%s and got " %(com['user']
+ , num, sides, add)
+ if len(result) < 11:
+ # the last one is the constant to add
+ for die in result[:-1]:
+ output += "%s, " % die
+ else:
+ output += "a lot of dice "
+
+ output += "for a total of %s" % sum(result)
+
+ bot.say(com['channel'], output)
lmgtfy = LetMeGoogleThatForYou()
sed = Sed()
-quit = QuitCommand()
+quit = Quit()
+dice = Dice()
View
44 hamper/plugins/friendly.py
@@ -4,32 +4,30 @@
from zope.interface import implements
-from hamper.interfaces import IPlugin
+from hamper.interfaces import Plugin
-class Friendly(object):
+class Friendly(Plugin):
"""Be polite. When people say hello, response."""
- implements(IPlugin)
name = 'friendly'
priority = 2
def setup(self, factory):
- self.greetings = ['hi', 'hello', 'hey']
+ self.greetings = ['hi', 'hello', 'hey', 'sup', 'yo', 'hola']
def process(self, bot, comm):
if not comm['directed']:
return
if comm['message'].strip() in self.greetings:
- bot.say('{0} {1[user]}'
+ bot.msg(comm['channel'], '{0} {1[user]}'
.format(random.choice(self.greetings), comm))
return True
-class OmgPonies(object):
+class OmgPonies(Plugin):
"""The classics never die."""
- implements(IPlugin)
name = 'ponies'
priority = 3
@@ -38,14 +36,15 @@ class OmgPonies(object):
def setup(self, factory):
self.last_pony_time = datetime.now()
- pass
def process(self, bot, comm):
if re.match(r'.*pon(y|ies).*', comm['message'], re.I):
now = datetime.now()
- since_last_pony = now - self.last_pony_time
- if since_last_pony.total_seconds() >= self.cooldown:
- bot.say('OMG!!! PONIES!!!')
+ since_last = now - self.last_pony_time
+ since_last = since_last.seconds + 24*3600*since_last.days
+
+ if since_last >= self.cooldown:
+ bot.msg(comm['channel'], 'OMG!!! PONIES!!!')
self.last_pony_time = now
else:
print('too many ponies')
@@ -54,5 +53,28 @@ def process(self, bot, comm):
return False
+class BotSnack(Plugin):
+ """Reward a good bot."""
+
+ name = 'botsnack'
+ priority = 0
+
+ def setup(self, factory):
+ self.rewards = {
+ 'botsnack': ['yummy', 'my favorite!'],
+ 'goodhamper': ['^_^', ':D'],
+ }
+
+ def process(self, bot, comm):
+ slug = comm['message'].lower().replace(' ', '')
+ for k, v in self.rewards.items():
+ if k in slug:
+ bot.say(comm['channel'], random.choice(v))
+ return True
+
+ return False
+
+
friendly = Friendly()
omgponies = OmgPonies()
+botsnack = BotSnack()
View
104 hamper/plugins/plugin_utils.py
@@ -3,58 +3,82 @@
from zope.interface import implements
from bravo import plugin
-from hamper.interfaces import Command, IPlugin
+from hamper.interfaces import Command, Plugin, IPlugin
-class PluginUtils(Command):
+class PluginUtils(Plugin):
name = 'plugins'
priority = 0
- regex = r'^plugins?\W+(.*)$'
- def command(self, bot, comm, groups):
- args = groups[0].split(' ')
- args = [a.strip() for a in args]
- args = [a for a in args if a]
+ class ListPlugins(Command):
+ regex = r'^plugins?(?: list)?$'
- dispatch = {
- 'list': self.listPlugins,
- 'reload': self.reloadPlugin,
- }
- print args
-
- if len(args) == 0:
- self.listPlugins(bot, *args)
+ def command(self, bot, comm, groups):
+ """Reply with a list of all currently loaded plugins."""
+ bot.msg(comm['channel'], 'Loaded Plugins: {0}.'.format(
+ ', '.join([c.name for c in bot.factory.plugins])))
return True
- if args[0] in dispatch:
- dispatch[args[0]](bot, *args)
+ class ReloadPlugins(Command):
+ regex = r'^plugins? reload (.*)$'
+ def command(self, bot, comm, groups):
+ """Reload a named plugin."""
+ name = groups[0]
+
+ ps = bot.factory.plugins
+ matched_plugins = [p for p in ps if p.name == name]
+ if len(matched_plugins) == 0:
+ bot.msg(comm['channel'], "I can't find a plugin named {0}!"
+ .format(name))
+ return False
+
+ target_plugin = matched_plugins[0]
+ # Fun fact: the fresh thing is just a dummy. It just can't be None
+ new_plugin = plugin.retrieve_named_plugins(IPlugin, [name],
+ 'hamper.plugins', {'fresh': True})[0]
+
+ bot.removePlugin(target_plugin)
+ bot.addPlugin(new_plugin)
+ bot.msg(comm['channel'], 'Reloading {0}.'.format(new_plugin))
return True
- def listPlugins(self, bot, *args):
- """Reply with a list of all currently loaded plugins."""
- bot.say('Loaded Plugins: {0}.'.format(
- ', '.join([c.name for c in bot.factory.plugins])))
-
- def reloadPlugin(self, bot, *args):
- """Reload a named plugin."""
- name = ' '.join(args[1:])
-
- ps = bot.factory.plugins
-
- matched_plugins = [p for p in ps if p.name == name]
- if len(matched_plugins) == 0:
- bot.say("I can't find a plugin named %s!" % name)
- return
-
- target_plugin = matched_plugins[0]
- # Fun fact: the fresh thing is just a dummy. It just can't be None
- new_plugin = plugin.retrieve_named_plugins(IPlugin, [name],
- 'hamper.plugins', {'fresh': True})[0]
+ class LoadPlugin(Command):
+ regex = r'^plugins? load (.*)$'
+ def command(self, bot, comm, groups):
+ """Load a named plugin."""
+ name = groups[0]
+ ps = bot.factory.plugins
+ matched_plugins = [p for p in ps if p.name == name]
+ if len(matched_plugins) != 0:
+ bot.msg(comm['channel'], "%s is already loaded." % name)
+ return False
+
+ # Fun fact: the fresh thing is just a dummy. It just can't be None
+ new_plugin = plugin.retrieve_named_plugins(IPlugin, [name],
+ 'hamper.plugins', {'fresh': True})[0]
+
+ bot.addPlugin(new_plugin)
+ bot.msg(comm['channel'], 'Loading {0}.'.format(new_plugin))
+ return True
- bot.removePlugin(target_plugin)
- bot.addPlugin(new_plugin)
- bot.say('Reloading {0}.'.format(new_plugin))
+ class UnloadPlugin(Command):
+ regex = r'^plugins? unload (.*)$'
+ def command(self, bot, comm, groups):
+ """Unload a named plugin."""
+ name = groups[0]
+ ps = bot.factory.plugins
+ matched_plugins = [p for p in ps if p.name == name]
+ if len(matched_plugins) == 0:
+ bot.msg(comm['channel'], "I can't find a plugin named {0}!"
+ .format(name))
+ return False
+
+ target_plugin = matched_plugins[0]
+
+ bot.removePlugin(target_plugin)
+ bot.msg(comm['channel'], 'Unloading {0}.'.format(target_plugin))
+ return True
plugin_utils = PluginUtils()
View
62 hamper/plugins/questions.py
@@ -0,0 +1,62 @@
+from random import random
+
+from hamper.interfaces import Plugin
+
+
+class Questions(Plugin):
+
+ name = 'questions'
+
+ def setup(self, *args):
+ """
+ Set up the list of responses, with weights. If the weight of a response
+ is 'eq', it will be assigned equal value after everything that has a
+ number is assigned. If it's weight is some fraction of 'eq' (ie: 'eq/2'
+ or 'eq/3'), then it will be assigned 1/2, 1/3, etc of the 'eq' weight.
+ All probabilities will up to 1.0 (plus/minus any rounding errors).
+ """
+
+ responses = [
+ ('I think... Yes.', 'eq'), ('Maybe. Possibly. It could be.', 'eq'),
+ ("No. No, I don't think so.", 'eq'), ("I don't know.", 'eq'),
+ ('Ask again later.', 'eq/2'), ('Without a doubt.', 'eq/2'),
+ ('Heck yes!', 'eq/2'), ("I'm sorry, I was thinking of bananas", .01),
+ ]
+
+ total_prob = 0
+ real_resp = []
+ evens = []
+ for resp, prob in responses:
+ if isinstance(prob, str):
+ if prob.startswith('eq'):
+ sp = prob.split('/')
+ if len(sp) == 1:
+ evens.append((resp, 1))
+ else:
+ div = int(sp[1])
+ evens.append((resp, 1.0/div))
+
+ else:
+ real_resp.append((resp, prob))
+ total_prob += prob
+
+ # Share is the probability of a "eq" probability. Share/2 would be the
+ # probability of a "eq/2" probability.
+ share = (1 - total_prob) / sum(div for _, div in evens)
+ for resp, divisor in evens:
+ real_resp.append((resp, share*divisor))
+
+ self.responses = real_resp
+
+ def process(self, bot, comm):
+ if comm['directed'] and comm['message'].strip()[-1] == '?':
+ r = random()
+ for resp, prob in self.responses:
+ r -= prob
+ if r < 0:
+ bot.say(comm['channel'], '{0}: {1}'
+ .format(comm['user'], resp))
+ return True
+ return False
+
+questions = Questions()
View
45 hamper/plugins/quote.py
@@ -6,44 +6,46 @@
from sqlalchemy.ext.declarative import declarative_base
import sqlalchemy
-from hamper.interfaces import Command
+from hamper.interfaces import Command, Plugin
SQLAlchemyBase = declarative_base()
-class Quotes(Command):
+
+class Quotes(Plugin):
'''Remember quotes, and recall on demand.'''
name = 'quotes'
priority = 0
- regex = r'^quotes?(?: +(.*))?$'
def setup(self, factory):
SQLAlchemyBase.metadata.create_all(factory.db_engine)
- def command(self, bot, comm, groups):
- if groups[0]:
- args = groups[0].split(' ')
- args = [a.strip() for a in args if a.strip()]
- else:
- args = []
-
- if len(args) == 0:
- # Deliver a quote
+ class DeliverQuote(Command):
+ """Deliver a quote."""
+ regex = r'^quotes?$'
+ def command(self, bot, comm, groups):
index = random.randrange(0, bot.db.query(Quote).count() + 1)
quote = bot.factory.db.query(Quote)[index]
# Lame twisted irc doesn't support unicode.
- bot.say(str(quote.text))
- elif args[0] == '--args':
- # Add a quote
- text = ' '.join(args[1:])
+ bot.msg(comm['channel'], str(quote.text))
+ return True
+
+ class AddQuote(Command):
+ """Add a quote."""
+ regex = r'^quotes? --add (.*)$'
+ def command(self, bot, comm, groups):
+ text = groups[0]
quote = Quote(text, comm['user'])
bot.factory.db.add(quote)
- bot.say('Succesfully added quote.')
- elif args[0] == '--count':
- bot.say('I know {0} quotes.'.format(bot.db.query(Quote).count()))
- else:
- bot.say('Wait, what?')
+ bot.msg(comm['channel'], 'Succesfully added quote.')
+
+ class CountQuotes(Command):
+ """Count how many quotes the bot knows."""
+ regex = r'^quotes? --count$'
+ def command(self, bot, comm, groups):
+ count = bot.db.query(Quote).count()
+ bot.msg(comm['channel'], 'I know {0} quotes.'.format(count))
class Quote(SQLAlchemyBase):
@@ -64,4 +66,5 @@ def __init__(self, text, adder, added=None):
self.adder = adder
self.added = added
+
quotes = Quotes()
View
2 scripts/hamper
@@ -13,7 +13,7 @@ if __name__ == '__main__':
config = yaml.load(open('hamper.conf'))
- for key in ['server', 'port', 'nickname', 'channel']:
+ for key in ['server', 'port', 'nickname', 'channels']:
if (key not in config) or (not config[key]):
print('You need to define {0} in the config file.'.format(key))
sys.exit();
View
2 setup.py
@@ -7,7 +7,7 @@
setup(name='hamper',
version=version.encode('utf8'),
description='Yet another IRC bot',
- install_requires=['pyyaml', 'Twisted'],
+ install_requires=['pyyaml', 'Twisted', 'SQLAlchemy'],
author='Mike Cooper',
author_email='mythmon@gmail.com',
url='https://www.github.com/hamperbot/hamper',

No commit comments for this range

Something went wrong with that request. Please try again.