diff --git a/ircbot/__init__.py b/ircbot/__init__.py index e69de29..97b4aaa 100644 --- a/ircbot/__init__.py +++ b/ircbot/__init__.py @@ -0,0 +1 @@ +"""Core IRC bot code, extended by plugins.""" diff --git a/ircbot/admin.py b/ircbot/admin.py index 11c02fc..88d305e 100644 --- a/ircbot/admin.py +++ b/ircbot/admin.py @@ -22,7 +22,6 @@ admin.site.register(IrcPlugin) def send_privmsg(request): """Send a privmsg over XML-RPC to the IRC bot.""" - if request.method == 'POST': form = PrivmsgForm(request.POST) if form.is_valid(): diff --git a/ircbot/bot.py b/ircbot/bot.py index 2c63602..886d553 100644 --- a/ircbot/bot.py +++ b/ircbot/bot.py @@ -29,17 +29,18 @@ log = logging.getLogger('ircbot.bot') class IrcBotXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): - """Override the basic request handler to change the logging.""" def log_message(self, format, *args): """Use a logger rather than stderr.""" - log.debug("XML-RPC - %s - %s", self.client_address[0], format%args) + log.debug("XML-RPC - %s - %s", self.client_address[0], format % args) class PrioritizedRegexHandler(collections.namedtuple('Base', ('priority', 'regex', 'callback'))): + """Regex handler that still uses the normal handler priority stuff.""" + def __lt__(self, other): - "when sorting prioritized handlers, only use the priority" + """When sorting prioritized handlers, only use the priority""" return self.priority < other.priority @@ -67,13 +68,11 @@ class DrReactor(irc.client.Reactor): def __init__(self, on_connect=__do_nothing, on_disconnect=__do_nothing): """Initialize our custom stuff.""" - super(DrReactor, self).__init__(on_connect=on_connect, on_disconnect=on_disconnect) self.regex_handlers = {} def server(self): """Creates and returns a ServerConnection object.""" - c = LenientServerConnection(self) with self.mutex: self.connections.append(c) @@ -102,7 +101,6 @@ class DrReactor(irc.client.Reactor): This is basically an extension of add_global_handler(), either may work, though it turns out most modules probably want this one. """ - if type(events) != list: events = [events] @@ -123,7 +121,6 @@ class DrReactor(irc.client.Reactor): Returns 1 on success, otherwise 0. """ - ret = 1 if type(events) != list: @@ -131,7 +128,7 @@ class DrReactor(irc.client.Reactor): for event in events: with self.mutex: - if not event in self.regex_handlers: + if event not in self.regex_handlers: ret = 0 for h in self.regex_handlers[event]: if handler == h.callback: @@ -143,7 +140,6 @@ class DrReactor(irc.client.Reactor): Also supports regex handlers. """ - try: log.debug("EVENT: e[%s] s[%s] t[%s] a[%s]", event.type, event.source, event.target, event.arguments) @@ -233,7 +229,6 @@ class DrReactor(irc.client.Reactor): event incoming event """ - log.debug("RECURSING EVENT: e[%s] s[%s] t[%s] a[%s]", event.type, event.source, event.target, event.arguments) @@ -244,7 +239,7 @@ class DrReactor(irc.client.Reactor): log.debug("checking it against %s", attempt) start_idx = attempt.find('[') - subcmd = attempt[start_idx+1:] + subcmd = attempt[start_idx + 1:] end_idx = subcmd.rfind(']') subcmd = subcmd[:end_idx] @@ -272,7 +267,7 @@ class DrReactor(irc.client.Reactor): # the text of that event now is, we should replace the parent # event's [] section with it. oldtext = event.arguments[0] - newtext = oldtext.replace('['+subcmd+']', newevent.arguments[0]) + newtext = oldtext.replace('[' + subcmd + ']', newevent.arguments[0]) log.debug("oldtext: '%s' newtext: '%s'", oldtext, newtext) event.arguments[0] = newtext @@ -296,7 +291,6 @@ class DrReactor(irc.client.Reactor): event incoming event """ - replacement = False replies = [] @@ -358,6 +352,7 @@ class IRCBot(irc.client.SimpleIRCClient): splitter = "..." def __init__(self, reconnection_interval=60): + """Initialize bot.""" super(IRCBot, self).__init__() self.channels = IRCDict() @@ -477,7 +472,6 @@ class IRCBot(irc.client.SimpleIRCClient): e.arguments[1] == channel e.arguments[2] == nick list """ - ch_type, channel, nick_list = e.arguments if channel == '*': @@ -529,7 +523,6 @@ class IRCBot(irc.client.SimpleIRCClient): event incoming event """ - what = event.arguments[0] log.debug("welcome: %s", what) @@ -563,7 +556,6 @@ class IRCBot(irc.client.SimpleIRCClient): msg -- Quit message. """ - self._xmlrpc_shutdown() self.connection.disconnect(msg) sys.exit(0) @@ -577,7 +569,6 @@ class IRCBot(irc.client.SimpleIRCClient): msg -- Quit message. """ - self.connection.disconnect(msg) def get_version(self): @@ -585,14 +576,11 @@ class IRCBot(irc.client.SimpleIRCClient): Used when answering a CTCP VERSION request. """ - return "Python irc.bot ({version})".format( version=irc.client.VERSION_STRING) - def jump_server(self, msg="Changing servers"): """Connect to a new server, potentially disconnecting from the current one.""" - if self.connection.is_connected(): self.connection.disconnect(msg) @@ -605,7 +593,6 @@ class IRCBot(irc.client.SimpleIRCClient): Replies to VERSION and PING requests and relays DCC requests to the on_dccchat method. """ - nick = e.source.nick if e.arguments[0] == "VERSION": c.ctcp_reply(nick, "VERSION " + self.get_version()) @@ -616,6 +603,7 @@ class IRCBot(irc.client.SimpleIRCClient): self.on_dccchat(c, e) def on_dccchat(self, c, e): + """Do nothing.""" pass def handle_load(self, connection, event, match): @@ -631,7 +619,6 @@ class IRCBot(irc.client.SimpleIRCClient): :param match: the matched regex (via add_global_regex_handler) :type match: Match """ - has_perm = ircbotlib.has_permission(event.source, 'ircbot.manage_loaded_plugins') log.debug("has permission to load?: %s", str(has_perm)) if has_perm: @@ -655,7 +642,6 @@ class IRCBot(irc.client.SimpleIRCClient): :param feedback: whether or not to send messages to IRC regarding the load attempt :type feedback: bool """ - log.debug("trying to load plugin %s", plugin_path) dest = None @@ -718,7 +704,6 @@ class IRCBot(irc.client.SimpleIRCClient): :param match: the matched regex (via add_global_regex_handler) :type match: Match """ - has_perm = ircbotlib.has_permission(event.source, 'ircbot.manage_loaded_plugins') log.debug("has permission to reload?: %s", str(has_perm)) if has_perm: @@ -738,7 +723,6 @@ class IRCBot(irc.client.SimpleIRCClient): :param feedback: whether or not to send messages to IRC regarding the reload attempt :type feedback: bool """ - log.debug("trying to unload plugin %s", plugin_path) dest = ircbotlib.reply_destination_for_event(event) @@ -807,7 +791,6 @@ class IRCBot(irc.client.SimpleIRCClient): :param match: the matched regex (via add_global_regex_handler) :type match: Match """ - has_perm = ircbotlib.has_permission(event.source, 'ircbot.manage_loaded_plugins') log.debug("has permission to unload?: %s", str(has_perm)) if has_perm: @@ -828,7 +811,6 @@ class IRCBot(irc.client.SimpleIRCClient): :param feedback: whether or not to send messages to IRC regarding the unload attempt :type feedback: bool """ - log.debug("trying to unload plugin %s", plugin_path) dest = ircbotlib.reply_destination_for_event(event) @@ -866,7 +848,6 @@ class IRCBot(irc.client.SimpleIRCClient): text the message to send """ - if not target: return @@ -895,7 +876,6 @@ class IRCBot(irc.client.SimpleIRCClient): The replystr if the event is inside recursion, or, potentially, "NO MORE" to stop other event handlers from acting. """ - if event: log.debug("in reply for e[%s] r[%s]", event, replystr) replypath = ircbotlib.reply_destination_for_event(event) @@ -960,33 +940,27 @@ class IRCBot(irc.client.SimpleIRCClient): :param name: the name to expose the method as :type name: str """ - if func and self.xmlrpc: if hasattr(func, '__call__'): self.xmlrpc.register_function(func, name) def _xmlrpc_listen(self): """Begin listening. Hopefully this was called in a new thread.""" - self.xmlrpc.serve_forever() def _xmlrpc_shutdown(self): """Shut down the XML-RPC server.""" - if self.xmlrpc is not None: self.xmlrpc.shutdown() self.xmlrpc.server_close() - def start(self): """Start the bot.""" - self._connect() super(IRCBot, self).start() def sigint_handler(self, signal, frame): """Cleanly shutdown on SIGINT.""" - log.debug("shutting down") for path, plugin in self.plugins: log.debug("trying to shut down %s", path) @@ -1002,6 +976,7 @@ class Channel(object): """A class for keeping information about an IRC channel.""" def __init__(self): + """Initialize channel object.""" self.userdict = IRCDict() self.operdict = IRCDict() self.voiceddict = IRCDict() @@ -1011,64 +986,56 @@ class Channel(object): def users(self): """Returns an unsorted list of the channel's users.""" - return list(self.userdict.keys()) def opers(self): """Returns an unsorted list of the channel's operators.""" - return list(self.operdict.keys()) def voiced(self): - """Returns an unsorted list of the persons that have voice - mode set in the channel.""" - + """Returns an unsorted list of the persons that have voice mode set in the channel.""" return list(self.voiceddict.keys()) def owners(self): """Returns an unsorted list of the channel's owners.""" - return list(self.ownerdict.keys()) def halfops(self): """Returns an unsorted list of the channel's half-operators.""" - return list(self.halfopdict.keys()) def has_user(self, nick): """Check whether the channel has a user.""" - return nick in self.userdict def is_oper(self, nick): """Check whether a user has operator status in the channel.""" - return nick in self.operdict def is_voiced(self, nick): """Check whether a user has voice mode set in the channel.""" - return nick in self.voiceddict def is_owner(self, nick): """Check whether a user has owner status in the channel.""" - return nick in self.ownerdict def is_halfop(self, nick): """Check whether a user has half-operator status in the channel.""" - return nick in self.halfopdict def add_user(self, nick): + """Add user.""" self.userdict[nick] = 1 def remove_user(self, nick): + """Remove user.""" for d in self.userdict, self.operdict, self.voiceddict: if nick in d: del d[nick] def change_nick(self, before, after): + """Handle a nick change.""" self.userdict[after] = self.userdict.pop(before) if before in self.operdict: self.operdict[after] = self.operdict.pop(before) @@ -1076,6 +1043,7 @@ class Channel(object): self.voiceddict[after] = self.voiceddict.pop(before) def set_userdetails(self, nick, details): + """Set user details.""" if nick in self.userdict: self.userdict[nick] = details @@ -1088,7 +1056,6 @@ class Channel(object): value -- Value """ - if mode == "o": self.operdict[value] = 1 elif mode == "v": @@ -1109,7 +1076,6 @@ class Channel(object): value -- Value """ - try: if mode == "o": del self.operdict[value] @@ -1125,34 +1091,44 @@ class Channel(object): pass def has_mode(self, mode): + """Return if mode is in channel modes.""" return mode in self.modes def is_moderated(self): + """Return if the channel is +m.""" return self.has_mode("m") def is_secret(self): + """Return if the channel is +s.""" return self.has_mode("s") def is_protected(self): + """Return if the channel is +p.""" return self.has_mode("p") def has_topic_lock(self): + """Return if the channel is +t.""" return self.has_mode("t") def is_invite_only(self): + """Return if the channel is +i.""" return self.has_mode("i") def has_allow_external_messages(self): + """Return if the channel is +n.""" return self.has_mode("n") def has_limit(self): + """Return if the channel is +l.""" return self.has_mode("l") def limit(self): + """Return the channel limit count.""" if self.has_limit(): return self.modes["l"] else: return None def has_key(self): + """Return if the channel is +k.""" return self.has_mode("k") diff --git a/ircbot/forms.py b/ircbot/forms.py index ec89820..80d3f37 100644 --- a/ircbot/forms.py +++ b/ircbot/forms.py @@ -8,7 +8,6 @@ log = logging.getLogger('markov.forms') class PrivmsgForm(Form): - """Accept a privmsg to send to the ircbot.""" target = CharField() diff --git a/ircbot/ircplugins/__init__.py b/ircbot/ircplugins/__init__.py index e69de29..5aa0902 100644 --- a/ircbot/ircplugins/__init__.py +++ b/ircbot/ircplugins/__init__.py @@ -0,0 +1 @@ +"""Some basic-level IRC plugins.""" diff --git a/ircbot/ircplugins/echo.py b/ircbot/ircplugins/echo.py index d4a5680..bde99b0 100644 --- a/ircbot/ircplugins/echo.py +++ b/ircbot/ircplugins/echo.py @@ -1,18 +1,17 @@ +"""Echo given string back to the user/channel.""" + import logging -from ircbot.lib import Plugin, reply_destination_for_event - +from ircbot.lib import Plugin log = logging.getLogger('ircbot.lib') class Echo(Plugin): - """Have IRC commands to do IRC things (join channels, quit, etc.).""" def start(self): """Set up the handlers.""" - self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], r'^!echo\s+(.*)$', self.handle_echo, -20) @@ -20,14 +19,12 @@ class Echo(Plugin): def stop(self): """Tear down handlers.""" - self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_echo) super(Echo, self).stop() def handle_echo(self, connection, event, match): """Handle the echo command... by echoing.""" - return self.bot.reply(event, match.group(1)) diff --git a/ircbot/lib.py b/ircbot/lib.py index 412f82f..1590d31 100644 --- a/ircbot/lib.py +++ b/ircbot/lib.py @@ -8,17 +8,14 @@ from django.core.exceptions import ObjectDoesNotExist from ircbot.models import BotUser - log = logging.getLogger('ircbot.lib') class Plugin(object): - """Plugin base class.""" def __init__(self, bot, connection, event): """Initialization stuff here --- global handlers, configs from database, so on.""" - self.bot = bot self.connection = connection self.event = event @@ -27,17 +24,14 @@ class Plugin(object): def start(self): """Initialization stuff here --- global handlers, configs from database, so on.""" - log.info("started %s", self.__class__.__name__) def stop(self): """Teardown stuff here --- unregister handlers, for example.""" - log.info("stopped %s", self.__class__.__name__) def _unencode_xml(self, text): """Convert <, >, & to their real entities.""" - text = text.replace('<', '<') text = text.replace('>', '>') text = text.replace('&', '&') @@ -46,7 +40,6 @@ class Plugin(object): def has_permission(source, permission): """Check if the provided event source is a bot admin.""" - try: bot_user = BotUser.objects.get(nickmask=source) log.debug("found bot user %s", bot_user) @@ -74,7 +67,6 @@ def reply_destination_for_event(event): is the reply destination. Otherwise, the source (assumed to be the speaker in a privmsg)'s nick is the reply destination. """ - if irc.client.is_channel(event.target): return event.target else: