Browse Source

more cleanup, improvements, fixes, whatever; restructured command files, but haven't done all of them yet

tags/v0.1^2
Ben Kurtovic 13 years ago
parent
commit
8c6fb2e8ba
8 changed files with 95 additions and 97 deletions
  1. +26
    -17
      bot/classes/base_command.py
  2. +16
    -8
      bot/classes/base_task.py
  3. +2
    -3
      bot/classes/connection.py
  4. +1
    -5
      bot/classes/data.py
  5. +16
    -10
      bot/commands/__init__.py
  6. +27
    -37
      bot/commands/help.py
  7. +4
    -14
      bot/commands/test.py
  8. +3
    -3
      bot/tasks/__init__.py

+ 26
- 17
bot/classes/base_command.py View File

@@ -1,32 +1,41 @@
# -*- coding: utf-8 -*-

class BaseCommand(object):
"""A base class for commands on IRC."""
"""A base class for commands on IRC.

This docstring is reported to the user when they use !help <command>.
"""
# This is the command's name, as reported to the user when they use !help:
name = "base_command"
# Hooks are "msg", "msg_private", "msg_public", and "join". "msg" is the
# default behavior; if you wish to override that, change the value in your
# command subclass:
hooks = ["msg"]

def __init__(self, connection):
self.connection = connection

def get_hooks(self):
"""Hooks are: 'msg', 'msg_private', 'msg_public', and 'join'. Return
the hooks you want this command to be called on."""
return []
def check(self, data):
"""Returns whether this command should be called in response to 'data'.

def get_help(self, command):
"""Return help information for the command, used by !help. return None
for no help. If a given class handles multiple commands, the command
variable can be used to return different help for each one."""
return None
Given a Data() instance, return True if we should respond to this
activity, or False if we should ignore it or it doesn't apply to us.

def check(self, data):
"""Given a Data() object, return True if we should respond to this
activity, or False if we should ignore it/it doesn't apply to us. Most
commands return True if data.command == 'command_name', otherwise
they return False."""
Most commands return True if data.command == self.name, otherwise they
return False. This is the default behavior of check(); you need only
override it if you wish to change that.
"""
if data.is_command and data.command == self.name:
return True
return False

def process(self, data):
"""Handle an activity (usually a message) on IRC. At this point, thanks
"""Main entry point for doing a command.

Handle an activity (usually a message) on IRC. At this point, thanks
to self.check() which is called automatically by command_handler, we
know this is something we should respond to, so (usually) a
'if data.command != "command_name": return' is unnecessary."""
'if data.command != "command_name": return' is unnecessary.
"""
pass

+ 16
- 8
bot/classes/base_task.py View File

@@ -5,15 +5,23 @@ class BaseTask(object):
task_name = None

def __init__(self):
"""This is called once immediately after the task class is loaded by
the task manager (in wiki.task_manager.load_class_from_file())."""
"""Constructor for new tasks.

This is called once immediately after the task class is loaded by
the task manager (in tasks._load_class_from_file()).
"""
pass

def run(self, **kwargs):
"""This is called directly by task_manager.start_task() and is the main
way to make a task do stuff. kwargs will be any keyword arguments
passed to start_task(), which are (of course) optional. The same task
instance is preserved between runs, so you can theoretically store data
in self (e.g. start_task('mytask', action='store', data='foo')) and
then use it later (e.g. start_task('mytask', action='save'))."""
"""Main entry point to run a given task.

This is called directly by tasks.start() and is the main way to make a
task do stuff. kwargs will be any keyword arguments passed to start()
which are entirely optional.

The same task instance is preserved between runs, so you can
theoretically store data in self (e.g.
start('mytask', action='store', data='foo')) and then use it later
(e.g. start('mytask', action='save')).
"""
pass

+ 2
- 3
bot/classes/connection.py View File

@@ -19,7 +19,7 @@ class Connection(object):
self.ident = ident
self.realname = realname

# A lock to prevent us from sending two messages at once.
# A lock to prevent us from sending two messages at once:
self.lock = threading.Lock()

def connect(self):
@@ -58,8 +58,7 @@ class Connection(object):
self.send(message)

def reply(self, data, msg):
"""Send a private message as a reply to a user on the server. `data` is
a Data object (or anything with chan and nick attributes)."""
"""Send a private message as a reply to a user on the server."""
message = "".join((chr(2), data.nick, chr(0x0f), ": ", msg))
self.say(data.chan, message)



+ 1
- 5
bot/classes/data.py View File

@@ -13,11 +13,7 @@ class Data(object):
def __init__(self, line):
self.line = line
self.chan = str()
self.nick = str()
self.ident = str()
self.host = str()
self.msg = str()
self.chan = self.nick = self.ident = self.host = self.msg = ""

def parse_args(self):
"""Parse command args from self.msg into self.command and self.args."""


+ 16
- 10
bot/commands/__init__.py View File

@@ -1,16 +1,23 @@
# -*- coding: utf-8 -*-

# A module to manage IRC commands.
"""
EarwigBot's IRC Command Manager

This package provides the IRC "commands" used by the bot's front-end component.
In __init__, you can find some functions used to load and run these commands.
"""

import os
import traceback

__all__ = ["load", "get_all", "check"]

_commands = []
# Store commands in a dict, where the key is the command's name and the value
# is an instance of the command's class:
_commands = {}

def _process_module(connection, module):
"""go through all objects in a module and add valid command classes to the commands variable"""
def _load_class_from_file(connection, module):
"""Add."""
global commands
objects = dir(module)

@@ -30,9 +37,9 @@ def _process_module(connection, module):
continue

def load(connection):
"""load all valid command classes from irc/commmands/ into the commands variable"""
files = os.listdir(os.path.join("irc", "commands")) # get all files in irc/commands/
files.sort() # alphabetically sort list of files
"""Load all valid commands into the _commands global variable."""
files = os.listdir(os.path.join("bot", "commands"))
files.sort()

for f in files:
if f.startswith("_") or not f.endswith(".py"): # ignore non-python files or files beginning with "_"
@@ -50,12 +57,11 @@ def load(connection):
print "Found %s command classes: %s." % (len(commands), ', '.join(pretty_cmnds))

def get_all():
"""Return our list of all commands."""
"""Return our dict of all loaded commands."""
return _commands

def check(hook, data):
"""Given an event on IRC, check if there's anything we can respond to by
calling each command class"""
"""Given an event on IRC, check if there's anything we can respond to."""
# parse command arguments into data.command and data.args
data.parse_args()



+ 27
- 37
bot/commands/help.py View File

@@ -1,54 +1,44 @@
# -*- coding: utf-8 -*-

# Generates help information.
from classes import BaseCommand, Data
import commands

from irc.classes import BaseCommand, Data
from irc import command_handler

class Help(BaseCommand):
def get_hooks(self):
return ["msg"]

def get_help(self, command):
return "Generates help information."

def check(self, data):
if data.is_command and data.command == "help":
return True
return False
class Command(BaseCommand):
"""Generates help information."""
name = "help"

def process(self, data):
self.cmnds = commands.get_all().keys()
if not data.args:
self.do_general_help(data)
self.do_main_help(data)
else:
if data.args[0] == "list":
self.do_list_help(data)
else:
self.do_command_help(data)

def do_general_help(self, data):
self.connection.reply(data, "I am a bot! You can get help for any command with '!help <command>', or a list of all loaded modules with '!help list'.")
self.do_command_help(data)

def do_list_help(self, data):
commands = command_handler.get_commands()
cmnds = map(lambda c: c.__class__.__name__, commands)
pretty_cmnds = ', '.join(cmnds)
self.connection.reply(data, "%s command classes loaded: %s." % (len(cmnds), pretty_cmnds))
def do_main_help(self, data):
"""Give the user a general help message with a list of all commands."""
msg = "I am a bot! I have {0} commands loaded: {1}. You can get help for any command with '!help <command>'."
msg.format(len(self.cmnds), ', '.join(self.cmnds))
self.connection.reply(data, msg)

def do_command_help(self, data):
"""Give the user help for a specific command."""
command = data.args[0]
commands = command_handler.get_commands()

dummy = Data() # dummy message to test which command classes pick up this command
dummy.command = command.lower() # lowercase command name
# Create a dummy message to test which commands pick up the user's
# input:
dummy = Data()
dummy.command = command.lower()
dummy.is_command = True

for cmnd in commands:
for cmnd in self.cmnds:
if cmnd.check(dummy):
help = cmnd.get_help(command)
doc = cmnd.__doc__
if doc:
msg = "info for command \x0303{0}\x0301: \"{1}\""
msg.format(command, doc)
self.connection.reply(data, msg)
return
break

try:
self.connection.reply(data, "info for command \x0303%s\x0301: \"%s\"" % (command, help))
except UnboundLocalError:
self.connection.reply(data, "sorry, no help for \x0303%s\x0301." % command)
msg = "sorry, no help for \x0303{0}\x0301.".format(command)
self.connection.reply(data, msg)

+ 4
- 14
bot/commands/test.py View File

@@ -1,22 +1,12 @@
# -*- coding: utf-8 -*-

# A very simple command to test the bot.

import random

from irc.classes import BaseCommand

class Test(BaseCommand):
def get_hooks(self):
return ["msg"]

def get_help(self, command):
return "Test the bot!"
from classes import BaseCommand

def check(self, data):
if data.is_command and data.command == "test":
return True
return False
class Command(BaseCommand):
"""Test the bot!"""
name = "test"

def process(self, data):
hey = random.randint(0, 1)


+ 3
- 3
bot/tasks/__init__.py View File

@@ -16,9 +16,9 @@ import config

__all__ = ["load", "schedule", "start"]

# store loaded tasks as a dict where the key is the task name and the value is
# an instance of the task class (wiki.tasks.task_file.Task())
_tasks = dict()
# Store loaded tasks as a dict where the key is the task name and the value is
# an instance of the task class:
_tasks = {}

def _load_class_from_file(f):
"""Look in a given file for the task class."""


Loading…
Cancel
Save