diff --git a/plugins/Network/README.txt b/plugins/Network/README.txt new file mode 100644 index 000000000..d60b47a97 --- /dev/null +++ b/plugins/Network/README.txt @@ -0,0 +1 @@ +Insert a description of your plugin here, with any notes, etc. about using it. diff --git a/plugins/Network/__init__.py b/plugins/Network/__init__.py new file mode 100644 index 000000000..28cc37784 --- /dev/null +++ b/plugins/Network/__init__.py @@ -0,0 +1,55 @@ +### +# Copyright (c) 2004, Jeremiah Fincher +# 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. +### + +""" +Includes commands for connecting, disconnecting, and reconnecting to multiple +networks, as well as several other utility functions related to IRC networks. +""" + +import supybot +import supybot.world as world + +__author__ = supybot.authors.jemfinch + +# This is a dictionary mapping supybot.Author instances to lists of +# contributions. +__contributors__ = {} + +import config +import plugin +reload(plugin) # In case we're being reloaded. + +if world.testing: + import test + +Class = plugin.Class +configure = config.configure + + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/plugins/Network/config.py b/plugins/Network/config.py new file mode 100644 index 000000000..5ca02671b --- /dev/null +++ b/plugins/Network/config.py @@ -0,0 +1,48 @@ +### +# Copyright (c) 2004, Jeremiah Fincher +# 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 supybot.conf as conf +import supybot.registry as registry + +def configure(advanced): + # This will be called by supybot to configure this module. advanced is + # a bool that specifies whether the user identified himself as an advanced + # user or not. You should effect your configuration by manipulating the + # registry as appropriate. + from supybot.questions import expect, anything, something, yn + conf.registerPlugin('Network', True) + + +Network = conf.registerPlugin('Network') +# This is where your configuration variables (if any) should go. For example: +# conf.registerGlobalValue(Network, 'someConfigVariableName', +# registry.Boolean(False, """Help for someConfigVariableName.""")) + + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78 diff --git a/plugins/Network/plugin.py b/plugins/Network/plugin.py new file mode 100644 index 000000000..2688f23fa --- /dev/null +++ b/plugins/Network/plugin.py @@ -0,0 +1,284 @@ +### +# Copyright (c) 2002-2004, Jeremiah Fincher +# 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 supybot + +import supybot.plugins as plugins + +import time + +import supybot.conf as conf +import supybot.utils as utils +import supybot.world as world +from supybot.commands import * +import supybot.ircmsgs as ircmsgs +import supybot.ircutils as ircutils +import supybot.privmsgs as privmsgs +import supybot.registry as registry +import supybot.callbacks as callbacks + +class Network(callbacks.Privmsg): + _whois = {} + _latency = {} + def _getIrc(self, network): + irc = world.getIrc(network) + if irc: + return irc + else: + raise callbacks.Error, \ + 'I\'m not currently connected to %s.' % network + + def connect(self, irc, msg, args, network, server, password): + """ [] [] + + Connects to another network (which will be represented by the name + provided in ) at . If port is not provided, it + defaults to 6667, the default port for IRC. If password is + provided, it will be sent to the server in a PASS command. + """ + try: + otherIrc = self._getIrc(network) + irc.error('I\'m already connected to %s.' % network, Raise=True) + except callbacks.Error: + pass + if server: + if ':' in server: + (server, port) = server.split(':') + port = int(port) + else: + port = 6667 + serverPort = (server, port) + else: + try: + serverPort = conf.supybot.networks.get(network).servers()[0] + except (registry.NonExistentRegistryEntry, IndexError): + irc.error('A server must be provided if the network is not ' + 'already registered.') + return + Owner = irc.getCallback('Owner') + newIrc = Owner._connect(network, serverPort=serverPort, + password=password) + conf.supybot.networks().add(network) + assert newIrc.callbacks is irc.callbacks, 'callbacks list is different' + irc.replySuccess('Connection to %s initiated.' % network) + connect = wrap(connect, ['owner', 'something', additional('something'), + additional('something', '')]) + + def disconnect(self, irc, msg, args, otherIrc, quitMsg): + """[] [] + + Disconnects from the network represented by the network . + If is given, quits the network with the given quit + message. is only necessary if the network is different + from the network the command is sent on. + """ + quitMsg = quitMsg or conf.supybot.plugins.Owner.quitMsg() or msg.nick + otherIrc.queueMsg(ircmsgs.quit(quitMsg)) + otherIrc.die() + conf.supybot.networks().discard(otherIrc.network) + if otherIrc != irc: + irc.replySuccess('Disconnection to %s initiated.' % + otherIrc.network) + disconnect = wrap(disconnect, ['owner', 'networkIrc', additional('text')]) + + def reconnect(self, irc, msg, args, otherIrc, quitMsg): + """[] [] + + Disconnects and then reconnects to . If no network is given, + disconnects and then reconnects to the network the command was given + on. If no quit message is given, uses the configured one + (supybot.plugins.Owner.quitMsg) or the nick of the person giving the + command. + """ + quitMsg = quitMsg or conf.supybot.plugins.Owner.quitMsg() or msg.nick + otherIrc.queueMsg(ircmsgs.quit(quitMsg)) + if otherIrc != irc: + # No need to reply if we're reconnecting ourselves. + irc.replySuccess() + reconnect = wrap(reconnect, ['owner', 'networkIrc', additional('text')]) + + def command(self, irc, msg, args, otherIrc, commandAndArgs): + """ [ ...] + + Gives the bot (with its associated s) on . + """ + self.Proxy(otherIrc, msg, commandAndArgs) + command = wrap(command, ['admin', ('networkIrc', True), many('something')]) + + ### + # whois command-related stuff. + ### + def do311(self, irc, msg): + nick = ircutils.toLower(msg.args[1]) + if (irc, nick) not in self._whois: + return + else: + self._whois[(irc, nick)][-1][msg.command] = msg + + # These are all sent by a WHOIS response. + do301 = do311 + do312 = do311 + do317 = do311 + do319 = do311 + do320 = do311 + + def do318(self, irc, msg): + nick = msg.args[1] + loweredNick = ircutils.toLower(nick) + if (irc, loweredNick) not in self._whois: + return + (replyIrc, replyMsg, d) = self._whois[(irc, loweredNick)] + hostmask = '@'.join(d['311'].args[2:4]) + user = d['311'].args[-1] + if '319' in d: + channels = d['319'].args[-1].split() + ops = [] + voices = [] + normal = [] + halfops = [] + for channel in channels: + if channel.startswith('@'): + ops.append(channel[1:]) + elif channel.startswith('%'): + halfops.append(channel[1:]) + elif channel.startswith('+'): + voices.append(channel[1:]) + else: + normal.append(channel) + L = [] + if ops: + L.append('is an op on %s' % utils.commaAndify(ops)) + if halfops: + L.append('is a halfop on %s' % utils.commaAndify(halfops)) + if voices: + L.append('is voiced on %s' % utils.commaAndify(voices)) + if normal: + if L: + L.append('is also on %s' % utils.commaAndify(normal)) + else: + L.append('is on %s' % utils.commaAndify(normal)) + else: + L = ['isn\'t on any non-secret channels'] + channels = utils.commaAndify(L) + if '317' in d: + idle = utils.timeElapsed(d['317'].args[2]) + signon = time.strftime(conf.supybot.reply.format.time(), + time.localtime(float(d['317'].args[3]))) + else: + idle = '' + signon = '' + if '312' in d: + server = d['312'].args[2] + else: + server = '' + if '301' in d: + away = ' %s is away: %s.' % (nick, d['301'].args[2]) + else: + away = '' + if '320' in d: + if d['320'].args[2]: + identify = ' identified' + else: + identify = '' + else: + identify = '' + s = '%s (%s) has been%s on server %s since %s (idle for %s) and ' \ + '%s.%s' % (user, hostmask, identify, server, signon, idle, + channels, away) + replyIrc.reply(s) + del self._whois[(irc, loweredNick)] + + def do402(self, irc, msg): + nick = msg.args[1] + loweredNick = ircutils.toLower(nick) + if (irc, loweredNick) not in self._whois: + return + (replyIrc, replyMsg, d) = self._whois[(irc, loweredNick)] + del self._whois[(irc, loweredNick)] + s = 'There is no %s on %s.' % (nick, irc.network) + replyIrc.reply(s) + do401 = do402 + + def whois(self, irc, msg, args, otherIrc, nick): + """[] + + Returns the WHOIS response gives for . is + only necessary if the network is different than the network the command + is sent on. + """ + # The double nick here is necessary because single-nick WHOIS only works + # if the nick is on the same server (*not* the same network) as the user + # giving the command. Yeah, it made me say wtf too. + nick = ircutils.toLower(nick) + otherIrc.queueMsg(ircmsgs.whois(nick, nick)) + self._whois[(otherIrc, nick)] = (irc, msg, {}) + whois = wrap(whois, ['networkIrc', 'nick']) + + def networks(self, irc, msg, args): + """takes no arguments + + Returns the networks to which the bot is currently connected. + """ + L = ['%s: %s' % (ircd.network, ircd.server) for ircd in world.ircs] + utils.sortBy(str.lower, L) + irc.reply(utils.commaAndify(L)) + networks = wrap(networks) + + def doPong(self, irc, msg): + now = time.time() + if irc in self._latency: + (replyIrc, when) = self._latency.pop(irc) + replyIrc.reply('%.2f seconds.' % (now-when)) + + def latency(self, irc, msg, args, otherIrc): + """[] + + Returns the current latency to . is only necessary + if the message isn't sent on the network to which this command is to + apply. + """ + otherIrc.queueMsg(ircmsgs.ping('Latency check (from %s).' % msg.nick)) + self._latency[otherIrc] = (irc, time.time()) + irc.noReply() + latency = wrap(latency, ['networkIrc']) + + def driver(self, irc, msg, args, otherIrc): + """[] + + Returns the current network driver for . is only + necessary if the message isn't sent on the network to which this + command is to apply. + """ + irc.reply(otherIrc.driver.__class__.__module__[8:]) + driver = wrap(driver, ['networkIrc']) + + +Class = Network + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/plugins/Network/test.py b/plugins/Network/test.py new file mode 100644 index 000000000..ac6c029be --- /dev/null +++ b/plugins/Network/test.py @@ -0,0 +1,43 @@ +### +# Copyright (c) 2002-2005, Jeremiah Fincher +# 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 NetworkTestCase(PluginTestCase): + plugins = ['Network', 'Utilities'] + def testNetworks(self): + self.assertNotError('networks') + + def testCommand(self): + self.assertResponse('network command %s echo 1' % self.irc.network, + '1') + + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: + diff --git a/setup.py b/setup.py index 445ca9bd6..30f6c551e 100644 --- a/setup.py +++ b/setup.py @@ -39,6 +39,7 @@ plugins = [ 'Format', 'Herald', 'Math', + 'Network', 'Misc', 'Owner', 'User',