do some ircbot prospector cleanup

bss/dr.botzo#17
This commit is contained in:
Brian S. Stephan 2017-03-10 18:51:36 -06:00
parent dbc4e6fe6f
commit 474afe2576
7 changed files with 30 additions and 65 deletions

View File

@ -0,0 +1 @@
"""Core IRC bot code, extended by plugins."""

View File

@ -22,7 +22,6 @@ admin.site.register(IrcPlugin)
def send_privmsg(request): def send_privmsg(request):
"""Send a privmsg over XML-RPC to the IRC bot.""" """Send a privmsg over XML-RPC to the IRC bot."""
if request.method == 'POST': if request.method == 'POST':
form = PrivmsgForm(request.POST) form = PrivmsgForm(request.POST)
if form.is_valid(): if form.is_valid():

View File

@ -29,17 +29,18 @@ log = logging.getLogger('ircbot.bot')
class IrcBotXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): class IrcBotXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
"""Override the basic request handler to change the logging.""" """Override the basic request handler to change the logging."""
def log_message(self, format, *args): def log_message(self, format, *args):
"""Use a logger rather than stderr.""" """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'))): class PrioritizedRegexHandler(collections.namedtuple('Base', ('priority', 'regex', 'callback'))):
"""Regex handler that still uses the normal handler priority stuff."""
def __lt__(self, other): 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 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): def __init__(self, on_connect=__do_nothing, on_disconnect=__do_nothing):
"""Initialize our custom stuff.""" """Initialize our custom stuff."""
super(DrReactor, self).__init__(on_connect=on_connect, on_disconnect=on_disconnect) super(DrReactor, self).__init__(on_connect=on_connect, on_disconnect=on_disconnect)
self.regex_handlers = {} self.regex_handlers = {}
def server(self): def server(self):
"""Creates and returns a ServerConnection object.""" """Creates and returns a ServerConnection object."""
c = LenientServerConnection(self) c = LenientServerConnection(self)
with self.mutex: with self.mutex:
self.connections.append(c) self.connections.append(c)
@ -102,7 +101,6 @@ class DrReactor(irc.client.Reactor):
This is basically an extension of add_global_handler(), either may This is basically an extension of add_global_handler(), either may
work, though it turns out most modules probably want this one. work, though it turns out most modules probably want this one.
""" """
if type(events) != list: if type(events) != list:
events = [events] events = [events]
@ -123,7 +121,6 @@ class DrReactor(irc.client.Reactor):
Returns 1 on success, otherwise 0. Returns 1 on success, otherwise 0.
""" """
ret = 1 ret = 1
if type(events) != list: if type(events) != list:
@ -131,7 +128,7 @@ class DrReactor(irc.client.Reactor):
for event in events: for event in events:
with self.mutex: with self.mutex:
if not event in self.regex_handlers: if event not in self.regex_handlers:
ret = 0 ret = 0
for h in self.regex_handlers[event]: for h in self.regex_handlers[event]:
if handler == h.callback: if handler == h.callback:
@ -143,7 +140,6 @@ class DrReactor(irc.client.Reactor):
Also supports regex handlers. Also supports regex handlers.
""" """
try: try:
log.debug("EVENT: e[%s] s[%s] t[%s] a[%s]", event.type, event.source, log.debug("EVENT: e[%s] s[%s] t[%s] a[%s]", event.type, event.source,
event.target, event.arguments) event.target, event.arguments)
@ -233,7 +229,6 @@ class DrReactor(irc.client.Reactor):
event incoming event event incoming event
""" """
log.debug("RECURSING EVENT: e[%s] s[%s] t[%s] a[%s]", event.type, event.source, log.debug("RECURSING EVENT: e[%s] s[%s] t[%s] a[%s]", event.type, event.source,
event.target, event.arguments) event.target, event.arguments)
@ -244,7 +239,7 @@ class DrReactor(irc.client.Reactor):
log.debug("checking it against %s", attempt) log.debug("checking it against %s", attempt)
start_idx = attempt.find('[') start_idx = attempt.find('[')
subcmd = attempt[start_idx+1:] subcmd = attempt[start_idx + 1:]
end_idx = subcmd.rfind(']') end_idx = subcmd.rfind(']')
subcmd = subcmd[:end_idx] 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 # the text of that event now is, we should replace the parent
# event's [] section with it. # event's [] section with it.
oldtext = event.arguments[0] 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) log.debug("oldtext: '%s' newtext: '%s'", oldtext, newtext)
event.arguments[0] = newtext event.arguments[0] = newtext
@ -296,7 +291,6 @@ class DrReactor(irc.client.Reactor):
event incoming event event incoming event
""" """
replacement = False replacement = False
replies = [] replies = []
@ -358,6 +352,7 @@ class IRCBot(irc.client.SimpleIRCClient):
splitter = "..." splitter = "..."
def __init__(self, reconnection_interval=60): def __init__(self, reconnection_interval=60):
"""Initialize bot."""
super(IRCBot, self).__init__() super(IRCBot, self).__init__()
self.channels = IRCDict() self.channels = IRCDict()
@ -477,7 +472,6 @@ class IRCBot(irc.client.SimpleIRCClient):
e.arguments[1] == channel e.arguments[1] == channel
e.arguments[2] == nick list e.arguments[2] == nick list
""" """
ch_type, channel, nick_list = e.arguments ch_type, channel, nick_list = e.arguments
if channel == '*': if channel == '*':
@ -529,7 +523,6 @@ class IRCBot(irc.client.SimpleIRCClient):
event incoming event event incoming event
""" """
what = event.arguments[0] what = event.arguments[0]
log.debug("welcome: %s", what) log.debug("welcome: %s", what)
@ -563,7 +556,6 @@ class IRCBot(irc.client.SimpleIRCClient):
msg -- Quit message. msg -- Quit message.
""" """
self._xmlrpc_shutdown() self._xmlrpc_shutdown()
self.connection.disconnect(msg) self.connection.disconnect(msg)
sys.exit(0) sys.exit(0)
@ -577,7 +569,6 @@ class IRCBot(irc.client.SimpleIRCClient):
msg -- Quit message. msg -- Quit message.
""" """
self.connection.disconnect(msg) self.connection.disconnect(msg)
def get_version(self): def get_version(self):
@ -585,14 +576,11 @@ class IRCBot(irc.client.SimpleIRCClient):
Used when answering a CTCP VERSION request. Used when answering a CTCP VERSION request.
""" """
return "Python irc.bot ({version})".format( return "Python irc.bot ({version})".format(
version=irc.client.VERSION_STRING) version=irc.client.VERSION_STRING)
def jump_server(self, msg="Changing servers"): def jump_server(self, msg="Changing servers"):
"""Connect to a new server, potentially disconnecting from the current one.""" """Connect to a new server, potentially disconnecting from the current one."""
if self.connection.is_connected(): if self.connection.is_connected():
self.connection.disconnect(msg) self.connection.disconnect(msg)
@ -605,7 +593,6 @@ class IRCBot(irc.client.SimpleIRCClient):
Replies to VERSION and PING requests and relays DCC requests Replies to VERSION and PING requests and relays DCC requests
to the on_dccchat method. to the on_dccchat method.
""" """
nick = e.source.nick nick = e.source.nick
if e.arguments[0] == "VERSION": if e.arguments[0] == "VERSION":
c.ctcp_reply(nick, "VERSION " + self.get_version()) c.ctcp_reply(nick, "VERSION " + self.get_version())
@ -616,6 +603,7 @@ class IRCBot(irc.client.SimpleIRCClient):
self.on_dccchat(c, e) self.on_dccchat(c, e)
def on_dccchat(self, c, e): def on_dccchat(self, c, e):
"""Do nothing."""
pass pass
def handle_load(self, connection, event, match): 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) :param match: the matched regex (via add_global_regex_handler)
:type match: Match :type match: Match
""" """
has_perm = ircbotlib.has_permission(event.source, 'ircbot.manage_loaded_plugins') has_perm = ircbotlib.has_permission(event.source, 'ircbot.manage_loaded_plugins')
log.debug("has permission to load?: %s", str(has_perm)) log.debug("has permission to load?: %s", str(has_perm))
if 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 :param feedback: whether or not to send messages to IRC regarding the load attempt
:type feedback: bool :type feedback: bool
""" """
log.debug("trying to load plugin %s", plugin_path) log.debug("trying to load plugin %s", plugin_path)
dest = None dest = None
@ -718,7 +704,6 @@ class IRCBot(irc.client.SimpleIRCClient):
:param match: the matched regex (via add_global_regex_handler) :param match: the matched regex (via add_global_regex_handler)
:type match: Match :type match: Match
""" """
has_perm = ircbotlib.has_permission(event.source, 'ircbot.manage_loaded_plugins') has_perm = ircbotlib.has_permission(event.source, 'ircbot.manage_loaded_plugins')
log.debug("has permission to reload?: %s", str(has_perm)) log.debug("has permission to reload?: %s", str(has_perm))
if 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 :param feedback: whether or not to send messages to IRC regarding the reload attempt
:type feedback: bool :type feedback: bool
""" """
log.debug("trying to unload plugin %s", plugin_path) log.debug("trying to unload plugin %s", plugin_path)
dest = ircbotlib.reply_destination_for_event(event) 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) :param match: the matched regex (via add_global_regex_handler)
:type match: Match :type match: Match
""" """
has_perm = ircbotlib.has_permission(event.source, 'ircbot.manage_loaded_plugins') has_perm = ircbotlib.has_permission(event.source, 'ircbot.manage_loaded_plugins')
log.debug("has permission to unload?: %s", str(has_perm)) log.debug("has permission to unload?: %s", str(has_perm))
if 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 :param feedback: whether or not to send messages to IRC regarding the unload attempt
:type feedback: bool :type feedback: bool
""" """
log.debug("trying to unload plugin %s", plugin_path) log.debug("trying to unload plugin %s", plugin_path)
dest = ircbotlib.reply_destination_for_event(event) dest = ircbotlib.reply_destination_for_event(event)
@ -866,7 +848,6 @@ class IRCBot(irc.client.SimpleIRCClient):
text the message to send text the message to send
""" """
if not target: if not target:
return return
@ -895,7 +876,6 @@ class IRCBot(irc.client.SimpleIRCClient):
The replystr if the event is inside recursion, or, potentially, The replystr if the event is inside recursion, or, potentially,
"NO MORE" to stop other event handlers from acting. "NO MORE" to stop other event handlers from acting.
""" """
if event: if event:
log.debug("in reply for e[%s] r[%s]", event, replystr) log.debug("in reply for e[%s] r[%s]", event, replystr)
replypath = ircbotlib.reply_destination_for_event(event) 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 :param name: the name to expose the method as
:type name: str :type name: str
""" """
if func and self.xmlrpc: if func and self.xmlrpc:
if hasattr(func, '__call__'): if hasattr(func, '__call__'):
self.xmlrpc.register_function(func, name) self.xmlrpc.register_function(func, name)
def _xmlrpc_listen(self): def _xmlrpc_listen(self):
"""Begin listening. Hopefully this was called in a new thread.""" """Begin listening. Hopefully this was called in a new thread."""
self.xmlrpc.serve_forever() self.xmlrpc.serve_forever()
def _xmlrpc_shutdown(self): def _xmlrpc_shutdown(self):
"""Shut down the XML-RPC server.""" """Shut down the XML-RPC server."""
if self.xmlrpc is not None: if self.xmlrpc is not None:
self.xmlrpc.shutdown() self.xmlrpc.shutdown()
self.xmlrpc.server_close() self.xmlrpc.server_close()
def start(self): def start(self):
"""Start the bot.""" """Start the bot."""
self._connect() self._connect()
super(IRCBot, self).start() super(IRCBot, self).start()
def sigint_handler(self, signal, frame): def sigint_handler(self, signal, frame):
"""Cleanly shutdown on SIGINT.""" """Cleanly shutdown on SIGINT."""
log.debug("shutting down") log.debug("shutting down")
for path, plugin in self.plugins: for path, plugin in self.plugins:
log.debug("trying to shut down %s", path) log.debug("trying to shut down %s", path)
@ -1002,6 +976,7 @@ class Channel(object):
"""A class for keeping information about an IRC channel.""" """A class for keeping information about an IRC channel."""
def __init__(self): def __init__(self):
"""Initialize channel object."""
self.userdict = IRCDict() self.userdict = IRCDict()
self.operdict = IRCDict() self.operdict = IRCDict()
self.voiceddict = IRCDict() self.voiceddict = IRCDict()
@ -1011,64 +986,56 @@ class Channel(object):
def users(self): def users(self):
"""Returns an unsorted list of the channel's users.""" """Returns an unsorted list of the channel's users."""
return list(self.userdict.keys()) return list(self.userdict.keys())
def opers(self): def opers(self):
"""Returns an unsorted list of the channel's operators.""" """Returns an unsorted list of the channel's operators."""
return list(self.operdict.keys()) return list(self.operdict.keys())
def voiced(self): def voiced(self):
"""Returns an unsorted list of the persons that have voice """Returns an unsorted list of the persons that have voice mode set in the channel."""
mode set in the channel."""
return list(self.voiceddict.keys()) return list(self.voiceddict.keys())
def owners(self): def owners(self):
"""Returns an unsorted list of the channel's owners.""" """Returns an unsorted list of the channel's owners."""
return list(self.ownerdict.keys()) return list(self.ownerdict.keys())
def halfops(self): def halfops(self):
"""Returns an unsorted list of the channel's half-operators.""" """Returns an unsorted list of the channel's half-operators."""
return list(self.halfopdict.keys()) return list(self.halfopdict.keys())
def has_user(self, nick): def has_user(self, nick):
"""Check whether the channel has a user.""" """Check whether the channel has a user."""
return nick in self.userdict return nick in self.userdict
def is_oper(self, nick): def is_oper(self, nick):
"""Check whether a user has operator status in the channel.""" """Check whether a user has operator status in the channel."""
return nick in self.operdict return nick in self.operdict
def is_voiced(self, nick): def is_voiced(self, nick):
"""Check whether a user has voice mode set in the channel.""" """Check whether a user has voice mode set in the channel."""
return nick in self.voiceddict return nick in self.voiceddict
def is_owner(self, nick): def is_owner(self, nick):
"""Check whether a user has owner status in the channel.""" """Check whether a user has owner status in the channel."""
return nick in self.ownerdict return nick in self.ownerdict
def is_halfop(self, nick): def is_halfop(self, nick):
"""Check whether a user has half-operator status in the channel.""" """Check whether a user has half-operator status in the channel."""
return nick in self.halfopdict return nick in self.halfopdict
def add_user(self, nick): def add_user(self, nick):
"""Add user."""
self.userdict[nick] = 1 self.userdict[nick] = 1
def remove_user(self, nick): def remove_user(self, nick):
"""Remove user."""
for d in self.userdict, self.operdict, self.voiceddict: for d in self.userdict, self.operdict, self.voiceddict:
if nick in d: if nick in d:
del d[nick] del d[nick]
def change_nick(self, before, after): def change_nick(self, before, after):
"""Handle a nick change."""
self.userdict[after] = self.userdict.pop(before) self.userdict[after] = self.userdict.pop(before)
if before in self.operdict: if before in self.operdict:
self.operdict[after] = self.operdict.pop(before) self.operdict[after] = self.operdict.pop(before)
@ -1076,6 +1043,7 @@ class Channel(object):
self.voiceddict[after] = self.voiceddict.pop(before) self.voiceddict[after] = self.voiceddict.pop(before)
def set_userdetails(self, nick, details): def set_userdetails(self, nick, details):
"""Set user details."""
if nick in self.userdict: if nick in self.userdict:
self.userdict[nick] = details self.userdict[nick] = details
@ -1088,7 +1056,6 @@ class Channel(object):
value -- Value value -- Value
""" """
if mode == "o": if mode == "o":
self.operdict[value] = 1 self.operdict[value] = 1
elif mode == "v": elif mode == "v":
@ -1109,7 +1076,6 @@ class Channel(object):
value -- Value value -- Value
""" """
try: try:
if mode == "o": if mode == "o":
del self.operdict[value] del self.operdict[value]
@ -1125,34 +1091,44 @@ class Channel(object):
pass pass
def has_mode(self, mode): def has_mode(self, mode):
"""Return if mode is in channel modes."""
return mode in self.modes return mode in self.modes
def is_moderated(self): def is_moderated(self):
"""Return if the channel is +m."""
return self.has_mode("m") return self.has_mode("m")
def is_secret(self): def is_secret(self):
"""Return if the channel is +s."""
return self.has_mode("s") return self.has_mode("s")
def is_protected(self): def is_protected(self):
"""Return if the channel is +p."""
return self.has_mode("p") return self.has_mode("p")
def has_topic_lock(self): def has_topic_lock(self):
"""Return if the channel is +t."""
return self.has_mode("t") return self.has_mode("t")
def is_invite_only(self): def is_invite_only(self):
"""Return if the channel is +i."""
return self.has_mode("i") return self.has_mode("i")
def has_allow_external_messages(self): def has_allow_external_messages(self):
"""Return if the channel is +n."""
return self.has_mode("n") return self.has_mode("n")
def has_limit(self): def has_limit(self):
"""Return if the channel is +l."""
return self.has_mode("l") return self.has_mode("l")
def limit(self): def limit(self):
"""Return the channel limit count."""
if self.has_limit(): if self.has_limit():
return self.modes["l"] return self.modes["l"]
else: else:
return None return None
def has_key(self): def has_key(self):
"""Return if the channel is +k."""
return self.has_mode("k") return self.has_mode("k")

View File

@ -8,7 +8,6 @@ log = logging.getLogger('markov.forms')
class PrivmsgForm(Form): class PrivmsgForm(Form):
"""Accept a privmsg to send to the ircbot.""" """Accept a privmsg to send to the ircbot."""
target = CharField() target = CharField()

View File

@ -0,0 +1 @@
"""Some basic-level IRC plugins."""

View File

@ -1,18 +1,17 @@
"""Echo given string back to the user/channel."""
import logging import logging
from ircbot.lib import Plugin, reply_destination_for_event from ircbot.lib import Plugin
log = logging.getLogger('ircbot.lib') log = logging.getLogger('ircbot.lib')
class Echo(Plugin): class Echo(Plugin):
"""Have IRC commands to do IRC things (join channels, quit, etc.).""" """Have IRC commands to do IRC things (join channels, quit, etc.)."""
def start(self): def start(self):
"""Set up the handlers.""" """Set up the handlers."""
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], r'^!echo\s+(.*)$', self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], r'^!echo\s+(.*)$',
self.handle_echo, -20) self.handle_echo, -20)
@ -20,14 +19,12 @@ class Echo(Plugin):
def stop(self): def stop(self):
"""Tear down handlers.""" """Tear down handlers."""
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_echo) self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_echo)
super(Echo, self).stop() super(Echo, self).stop()
def handle_echo(self, connection, event, match): def handle_echo(self, connection, event, match):
"""Handle the echo command... by echoing.""" """Handle the echo command... by echoing."""
return self.bot.reply(event, match.group(1)) return self.bot.reply(event, match.group(1))

View File

@ -8,17 +8,14 @@ from django.core.exceptions import ObjectDoesNotExist
from ircbot.models import BotUser from ircbot.models import BotUser
log = logging.getLogger('ircbot.lib') log = logging.getLogger('ircbot.lib')
class Plugin(object): class Plugin(object):
"""Plugin base class.""" """Plugin base class."""
def __init__(self, bot, connection, event): def __init__(self, bot, connection, event):
"""Initialization stuff here --- global handlers, configs from database, so on.""" """Initialization stuff here --- global handlers, configs from database, so on."""
self.bot = bot self.bot = bot
self.connection = connection self.connection = connection
self.event = event self.event = event
@ -27,17 +24,14 @@ class Plugin(object):
def start(self): def start(self):
"""Initialization stuff here --- global handlers, configs from database, so on.""" """Initialization stuff here --- global handlers, configs from database, so on."""
log.info("started %s", self.__class__.__name__) log.info("started %s", self.__class__.__name__)
def stop(self): def stop(self):
"""Teardown stuff here --- unregister handlers, for example.""" """Teardown stuff here --- unregister handlers, for example."""
log.info("stopped %s", self.__class__.__name__) log.info("stopped %s", self.__class__.__name__)
def _unencode_xml(self, text): def _unencode_xml(self, text):
"""Convert &lt;, &gt;, &amp; to their real entities.""" """Convert &lt;, &gt;, &amp; to their real entities."""
text = text.replace('&lt;', '<') text = text.replace('&lt;', '<')
text = text.replace('&gt;', '>') text = text.replace('&gt;', '>')
text = text.replace('&amp;', '&') text = text.replace('&amp;', '&')
@ -46,7 +40,6 @@ class Plugin(object):
def has_permission(source, permission): def has_permission(source, permission):
"""Check if the provided event source is a bot admin.""" """Check if the provided event source is a bot admin."""
try: try:
bot_user = BotUser.objects.get(nickmask=source) bot_user = BotUser.objects.get(nickmask=source)
log.debug("found bot user %s", bot_user) 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 is the reply destination. Otherwise, the source (assumed to be the speaker
in a privmsg)'s nick is the reply destination. in a privmsg)'s nick is the reply destination.
""" """
if irc.client.is_channel(event.target): if irc.client.is_channel(event.target):
return event.target return event.target
else: else: