diff --git a/plugins/LogToIrc/__init__.py b/plugins/LogToIrc/__init__.py new file mode 100644 index 000000000..88d827376 --- /dev/null +++ b/plugins/LogToIrc/__init__.py @@ -0,0 +1,60 @@ +# -*- coding:utf-8 -*- + +### +# Copyright (c) 2004, Stéphan Kochen +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * 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. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# 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. +### + +""" +Allows for sending the bot's logging output to channels or users. +""" + +import supybot +import supybot.world as world +import importlib + +__author__ = supybot.authors.jemfinch +__maintainer__ = supybot.authors.limnoria_core + +# Use this for the version of this plugin. You may wish to put a CVS keyword +# in here if you\'re keeping the plugin in CVS or some similar system. +__version__ = "%%VERSION%%" + +# This is a dictionary mapping supybot.Author instances to lists of +# contributions. +__contributors__ = {} + +from . import config +from . import plugin +from importlib import reload +importlib.reload(plugin) # In case we're being reloaded. + +if world.testing: + from . import test + +Class = plugin.Class +configure = config.configure diff --git a/plugins/LogToIrc/config.py b/plugins/LogToIrc/config.py new file mode 100644 index 000000000..b39af6189 --- /dev/null +++ b/plugins/LogToIrc/config.py @@ -0,0 +1,114 @@ +### +# Copyright (c) 2004, Stéphan Kochen +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * 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. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# 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 logging + +import supybot.log as log +import supybot.conf as conf +import supybot.ircutils as ircutils +import supybot.registry as registry + +from .handler import _ircHandler + + +class IrcLogLevel(log.ValidLogLevel): + """Value must be one of INFO, WARNING, ERROR, or CRITICAL.""" + minimumLevel = logging.INFO + def setValue(self, v): + log.ValidLogLevel.setValue(self, v) + _ircHandler.setLevel(self.value) + +class ValidChannelOrNick(registry.String): + """Value must be a valid channel or a valid nick.""" + def setValue(self, v): + if not (ircutils.isNick(v) or ircutils.isChannel(v)): + self.error() + registry.String.setValue(self, v) + +class Targets(registry.SpaceSeparatedListOfStrings): + Value = ValidChannelOrNick + +conf.registerPlugin('LogToIrc') +conf.registerChannelValue(conf.supybot.plugins.LogToIrc, 'level', + IrcLogLevel(logging.WARNING, """Determines what the minimum priority + level logged will be to IRC. See supybot.log.level for possible + values. DEBUG is disabled due to the large quantity of output."""), + opSettable=False) +conf.registerNetworkValue(conf.supybot.plugins.LogToIrc, 'targets', + Targets([], """Determines which channels/nicks the bot should + log to. If no channels/nicks are set, this plugin will effectively be + turned off.""")) +conf.registerGlobalValue(conf.supybot.plugins.LogToIrc, 'networks', + registry.SpaceSeparatedSetOfStrings([], """Determines what networks the + bot should log to. If no networks are set, the bot will log on one network + (whichever happens to be around at the time it feels like logging).""")) +conf.registerNetworkValue(conf.supybot.plugins.LogToIrc, 'channelModesRequired', + registry.String('s', """Determines what channel modes a channel will be + required to have for the bot to log to the channel. If this string is + empty, no modes will be checked.""")) +conf.registerGlobalValue(conf.supybot.plugins.LogToIrc, + 'userCapabilityRequired', registry.String('owner', """Determines what + capability is required for the bot to log to in private messages to the + user. If this is empty, there will be no capability that's checked.""")) +conf.registerChannelValue(conf.supybot.plugins.LogToIrc, 'color', + registry.Boolean(False, """Determines whether the bot's logs to IRC will be + colorized with mIRC colors.""")) +conf.registerChannelValue(conf.supybot.plugins.LogToIrc, 'notice', + registry.Boolean(False, """Determines whether the bot's logs to IRC will be + sent via NOTICE instead of PRIVMSG. Channels will always be PRIVMSGed, + regardless of this variable; NOTICEs will only be used if this variable is + True and the target is a nick, not a channel.""")) + +def configure(advanced): + from supybot.questions import something, anything, yn, output + output("""Here you can set which channels and who the bot has to send log + messages to. Note that by default in order to log to a channel + the channel has to have mode +s set. Logging to a user requires + the user to have the Owner capability.""") + targets = '' + while not targets: + try: + targets = anything('Which channels or users would you like to ' + 'send log messages to?') + conf.supybot.plugins.LogToIrc.targets.set(targets) + except registry.InvalidRegistryValue as e: + output(str(e)) + targets = '' + colorized = yn('Would you like these messages to be colored?') + conf.supybot.plugins.LogToIrc.color.setValue(colorized) + if advanced: + level = '' + while not level: + try: + level = something('What would you like the minimum priority ' + 'level to be which will be logged to IRC?') + conf.supybot.plugins.LogToIrc.level.set(level) + except registry.InvalidRegistryValue as e: + output(str(e)) + level = '' diff --git a/plugins/LogToIrc/handler.py b/plugins/LogToIrc/handler.py new file mode 100644 index 000000000..d0137d8c4 --- /dev/null +++ b/plugins/LogToIrc/handler.py @@ -0,0 +1,143 @@ +### +# Copyright (c) 2004, Stéphan Kochen +# Copyright (c) 2021, Valentin Lorentz +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * 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. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# 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. +### + +"""This module MUST NOT be reloaded after config.py, because it would cause +the log level to be unset, ie. to log EVERYTHING, and that's bad +""" + +import logging + + +import supybot.log as log +import supybot.conf as conf +import supybot.utils as utils +import supybot.world as world +import supybot.ircmsgs as ircmsgs +import supybot.ircutils as ircutils +import supybot.registry as registry + + +class IrcHandler(logging.Handler): + def emit(self, record): + config = conf.supybot.plugins.LogToIrc + try: + s = utils.str.normalizeWhitespace(self.format(record)) + except: + self.handleError(record) + return + for irc in world.ircs: + network = irc.network + if irc.driver is None: + continue + for target in config.targets.getSpecific(network=irc.network)(): + msgmaker = ircmsgs.privmsg + if config.notice.getSpecific(target, network)() \ + and not irc.isChannel(target): + msgmaker = ircmsgs.notice + msg = msgmaker(target, s) + try: + if not irc.driver.connected: + continue + except AttributeError as e: + import traceback + traceback.print_exc() + continue + networks = conf.supybot.plugins.LogToIrc.networks() + if networks and irc.network not in networks: + continue + msgOk = True + if target in irc.state.channels: + channel = irc.state.channels[target] + modes = config.channelModesRequired.getSpecific( + network=network)() + for modeChar in modes: + if modeChar not in channel.modes: + msgOk = False + else: + capability = config.userCapabilityRequired.getSpecific() + if capability: + try: + hostmask = irc.state.nicksToHostmasks[target] + except KeyError: + msgOk = False + continue + if not ircdb.checkCapability(hostmask, capability): + msgOk = False + if msgOk: + # We use sendMsg here because queueMsg can cause some + # WARNING logs, which might be sent here, which might + # cause some more WARNING logs, etc. and that would be + # baaaaaad. + irc.sendMsg(msg) + else: + print('*** Not sending to %s @ %s' % + (utils.str.quoted(target), irc.network)) + + +class IrcFormatter(log.Formatter): + def formatException(self, xxx_todo_changeme): + (E, e, tb) = xxx_todo_changeme + L = [utils.exnToString(e), '::'] + frames = utils.stackTrace(frame=tb.tb_frame).split() + L.extend(frames) + del tb + while sum(map(len, L)) > 350: + L.pop() + return ' '.join(L) + + +class ColorizedIrcFormatter(IrcFormatter): + def formatException(self, xxx_todo_changeme1): + (E, e, tb) = xxx_todo_changeme1 + if conf.supybot.plugins.LogToIrc.color(): + s = IrcFormatter.formatException(self, (E, e, tb)) + return ircutils.mircColor(s, fg='red') + else: + return IrcFormatter.formatException(self, (E, e, tb)) + + def format(self, record, *args, **kwargs): + s = IrcFormatter.format(self, record, *args, **kwargs) + if conf.supybot.plugins.LogToIrc.color(): + if record.levelno == logging.CRITICAL: + s = ircutils.bold(ircutils.bold(s)) + elif record.levelno == logging.ERROR: + s = ircutils.mircColor(s, fg='red') + elif record.levelno == logging.WARNING: + s = ircutils.mircColor(s, fg='yellow') + return s + + +_ircHandler = IrcHandler() +_formatString = '%(name)s: %(levelname)s %(message)s' +_ircFormatter = ColorizedIrcFormatter(_formatString) +_ircHandler.setFormatter(_ircFormatter) + + + diff --git a/plugins/LogToIrc/plugin.py b/plugins/LogToIrc/plugin.py new file mode 100644 index 000000000..53360246d --- /dev/null +++ b/plugins/LogToIrc/plugin.py @@ -0,0 +1,74 @@ +# -*- coding:utf-8 -*- + +### +# Copyright (c) 2004, Stéphan Kochen +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * 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. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# 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. +### + +""" +Allows for sending the bot's logging output to channels or users. +""" + +import supybot.plugins as plugins + +import logging +import os.path + +import supybot.log as log +import supybot.conf as conf +import supybot.ircdb as ircdb +import supybot.ircutils as ircutils +import supybot.registry as registry +import supybot.callbacks as callbacks + + +from .handler import _ircHandler + + +class LogToIrc(callbacks.Privmsg): + threaded = True + def __init__(self, irc): + self.__parent = super(LogToIrc, self) + self.__parent.__init__(irc) + log._logger.addHandler(_ircHandler) + + def die(self): + self.__parent.die() + log._logger.removeHandler(_ircHandler) + + def do376(self, irc, msg): + targets = self.registryValue('targets') + for target in targets: + if irc.isChannel(target): + networkGroup = conf.supybot.networks.get(irc.network) + irc.queueMsg(networkGroup.channels.join(target)) + do377 = do422 = do376 + + +Class = LogToIrc + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/plugins/LogToIrc/test.py b/plugins/LogToIrc/test.py new file mode 100644 index 000000000..718612c9b --- /dev/null +++ b/plugins/LogToIrc/test.py @@ -0,0 +1,36 @@ +### +# Copyright (c) 2004, Stéphan Kochen +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * 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. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# 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. +### + +from supybot.test import * + +class LogToIrcTestCase(PluginTestCase): + plugins = ('LogToIrc',) + + +# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: