diff --git a/scripts/supybot b/scripts/supybot index 638d0c089..8b1156929 100755 --- a/scripts/supybot +++ b/scripts/supybot @@ -208,7 +208,6 @@ if __name__ == '__main__': defaultNetwork = conf.supybot.networks.default() network = conf.supybot.networks.get(defaultNetwork) server = network.server() - password = network.password() if ':' in server: serverAndPort = server.split(':', 1) serverAndPort[1] = int(serverAndPort[1]) @@ -251,8 +250,7 @@ if __name__ == '__main__': import supybot.callbacks as callbacks import supybot.Owner as Owner - irc = irclib.Irc(nick, user=user, ident=ident, - network=defaultNetwork, password=password) + irc = irclib.Irc(network=defaultNetwork) callback = Owner.Class() irc.addCallback(callback) driver = drivers.newDriver(server, irc) diff --git a/src/conf.py b/src/conf.py index 08566087a..c1e26374b 100644 --- a/src/conf.py +++ b/src/conf.py @@ -129,6 +129,9 @@ class ValidNick(registry.String): else: registry.String.setValue(self, v) +class ValidNicks(registry.SpaceSeparatedListOf): + Value = ValidNick + class ValidChannel(registry.String): """Value must be a valid IRC channel name.""" def setValue(self, v): @@ -142,7 +145,12 @@ class ValidChannel(registry.String): registry.String.setValue(self, v) registerGlobalValue(supybot, 'nick', - ValidNick('supybot', """Determines the bot's nick.""")) + ValidNick('supybot', """Determines the bot's default nick.""")) + +registerGlobalValue(supybot.nick, 'alternates', ValidNicks([], """Determines + what alternative nicks will be used if the primary nick (supybot.nick) + isn't available. If none are given, or if all are taken, the primary nick + will be perturbed appropriately until an unused nick is found.""")) registerGlobalValue(supybot, 'ident', ValidNick('supybot', """Determines the bot's ident string, if the server @@ -450,13 +458,6 @@ registerChannelValue(supybot.replies, 'possibleBug', # End supybot.replies. ### -# XXX: This should be SpaceSeparated, if it survives. -supybot.register('nickmods', registry.CommaSeparatedListOfStrings( - '__%s__,%s^,%s`,%s_,%s__,_%s,__%s,[%s]'.split(','), - """A list of modifications to be made to a nick when the nick the bot tries - to get from the server is in use. There should be one %s in each string; - this will get replaced with the original nick.""")) - registerGlobalValue(supybot, 'snarfThrottle', registry.Float(10.0, """A floating point number of seconds to throttle snarfed URLs, in order to prevent loops between two bots snarfing the same diff --git a/src/irclib.py b/src/irclib.py index ffd955e37..562b0177b 100644 --- a/src/irclib.py +++ b/src/irclib.py @@ -36,6 +36,7 @@ import supybot.fix as fix import copy import sets import time +import operator from itertools import imap, chain, cycle import supybot.log as log @@ -332,7 +333,8 @@ class IrcState(IrcCommandDispatcher): chan = ChannelState() chan.addUser(msg.nick) self.channels[channel] = chan - assert msg.nick == irc.nick, msg + # I don't know why this assert was here. + #assert msg.nick == irc.nick, msg def doMode(self, irc, msg): channel = msg.args[0] @@ -441,65 +443,86 @@ class Irc(IrcCommandDispatcher): _nickSetters = sets.Set(['001', '002', '003', '004', '250', '251', '252', '254', '255', '265', '266', '372', '375', '376', '333', '353', '332', '366', '005']) - def __init__(self, nick, user='', ident='', - network='unset', password='', callbacks=None): + def __init__(self, network, callbacks=None): world.ircs.append(self) - self.originalNick = intern(nick) - self.originalNetwork = intern(network) - self.nick = self.originalNick - self.network = self.originalNetwork - self.nickmods = cycle(conf.supybot.nickmods()) - self.password = password - self.user = intern(user or nick) # Default to nick - self.ident = intern(ident or nick) # Ditto. - self.prefix = '%s!%s@%s' % (nick, ident, 'unset.domain') + self.network = network if callbacks is None: self.callbacks = [] else: self.callbacks = callbacks self.state = IrcState() self.queue = IrcMsgQueue() - self.lastTake = 0 - self.server = 'unset' - self.afterConnect = False self.fastqueue = smallqueue() - self.lastping = time.time() - self.outstandingPing = False self.driver = None # The driver should set this later. - if self.password: - self.queue.enqueue(ircmsgs.password(self.password)) - log.info('Sending NICK command, nick is %s.', self.nick) - self.queue.enqueue(ircmsgs.nick(self.nick)) - log.info('Sending USER command, ident is %s, user is %s.', - self.nick, self.user) - self.queue.enqueue(ircmsgs.user(self.ident, self.user)) + self._setNonResettingVariables() + self._queueConnectMessages() def reset(self): """Resets the Irc object. Called when the driver reconnects.""" - self.nick = self.originalNick - self.network = self.originalNetwork - self.prefix = '%s!%s@%s' % (self.nick, self.ident, 'unset.domain') + self._setNonResettingVariables() + self._queueConnectMessages() self.state.reset() self.queue.reset() + self.fastqueue.reset() + for callback in self.callbacks: + callback.reset() + + def _setNonResettingVariables(self): + # Configuration stuff. + self.nick = conf.supybot.nick() + self.user = conf.supybot.user() + self.ident = conf.supybot.ident() + self.alternateNicks = conf.supybot.nick.alternates()[:] + self.password = conf.supybot.networks.get(self.network).password() + self.prefix = '%s!%s@%s' % (self.nick, self.ident, 'unset.domain') + # The rest. + self.lastTake = 0 self.server = 'unset' self.afterConnect = False self.lastping = time.time() self.outstandingPing = False - self.fastqueue = queue() + + def _queueConnectMessages(self): if self.password: - self.queue.enqueue(ircmsgs.password(self.password)) - self.queue.enqueue(ircmsgs.nick(self.nick)) - self.queue.enqueue(ircmsgs.user(self.ident, self.user)) - for callback in self.callbacks: - callback.reset() + log.info('Sending PASS command, not logging the password.') + self.queueMsg(ircmsgs.password(self.password)) + log.info('Sending NICK command, nick is %s.', self.nick) + self.queueMsg(ircmsgs.nick(self.nick)) + log.info('Sending USER command, ident is %s, user is %s.', + self.ident, self.user) + self.queueMsg(ircmsgs.user(self.ident, self.user)) + + def _getNextNick(self): + if self.alternateNicks: + return self.alternateNicks.pop(0) + else: + nick = conf.supybot.nick() + for c in '`_^': + if nick.endswith(c): + if len(nick) >= 9: # The max length on many servers. + nick = nick.rstrip(c) + continue + else: + return nick + c + else: + return nick + c + for c in '`_^': + if nick.startswith(c): + if len(nick) >= 9: + nick = nick.lstrip(c) + continue + else: + return c + nick + else: + return c + nick def __repr__(self): - return '' % self.server + return '' % self.network def addCallback(self, callback): """Adds a callback to the callbacks list.""" self.callbacks.append(callback) - utils.sortBy(lambda cb: cb.priority, self.callbacks) + utils.sortBy(operator.attrgetter('priority'), self.callbacks) def getCallback(self, name): """Gets a given callback by name.""" @@ -600,15 +623,16 @@ class Irc(IrcCommandDispatcher): def do376(self, msg): log.info('Got end of MOTD from %s', self.server) self.afterConnect = True + # Let's reset nicks in case we had to use a weird one. + self.alternateNicks = conf.supybot.nick.alternates()[:] do377 = do422 = do376 def do433(self, msg): """Handles 'nickname already in use' messages.""" - if self.nick != self.originalNick or not self.afterConnect: - newNick = self.nickmods.next() % self.originalNick - log.info('Got 433: %s is in use. Trying %s.', self.nick, newNick) - self.sendMsg(ircmsgs.nick(newNick)) - do432 = do433 + newNick = self._getNextNick() + log.info('Got 433: %s is in use. Trying %s.', self.nick, newNick) + self.sendMsg(ircmsgs.nick(newNick)) + do432 = do433 # 432: Erroneous nickname. def doJoin(self, msg): if msg.nick == self.nick: diff --git a/test/test.py b/test/test.py index 0f0fe034e..5ef656a97 100755 --- a/test/test.py +++ b/test/test.py @@ -52,6 +52,7 @@ supybot.log.detailedTracebacks: False supybot.throttleTime: 0 supybot.prefixChars: @ supybot.protocols.irc.throttleTime: -1 +supybot.networks.test.server: should.not.need.this """) fd.close() diff --git a/test/test_irclib.py b/test/test_irclib.py index 3a185a2e3..876998cf6 100644 --- a/test/test_irclib.py +++ b/test/test_irclib.py @@ -304,7 +304,7 @@ class IrcStateTestCase(SupyTestCase): class IrcTestCase(SupyTestCase): def setUp(self): - self.irc = irclib.Irc('nick') + self.irc = irclib.Irc('test') _ = self.irc.takeMsg() # NICK _ = self.irc.takeMsg() # USER @@ -325,6 +325,8 @@ class IrcTestCase(SupyTestCase): self.failUnless(msg.command == 'NICK' and msg.args[0] != self.irc.nick) def testSendBeforeQueue(self): + while self.irc.takeMsg() is not None: + self.irc.takeMsg() self.irc.queueMsg(ircmsgs.IrcMsg('NOTICE #foo bar')) self.irc.sendMsg(ircmsgs.IrcMsg('PRIVMSG #foo yeah!')) msg = self.irc.takeMsg() @@ -392,23 +394,35 @@ class IrcCallbackTestCase(SupyTestCase): self.assertEqual(doCommandCatcher.L, commands) def testFirstCommands(self): - nick = 'nick' - user = 'user any user' - password = 'password' - expected = [ircmsgs.nick(nick), ircmsgs.user(nick, user)] - irc = irclib.Irc(nick, user) - msgs = [irc.takeMsg()] - while msgs[-1] != None: - msgs.append(irc.takeMsg()) - msgs.pop() - self.assertEqual(msgs, expected) - irc = irclib.Irc(nick, user, password=password) - msgs = [irc.takeMsg()] - while msgs[-1] != None: - msgs.append(irc.takeMsg()) - msgs.pop() - expected.insert(0, ircmsgs.password(password)) - self.assertEqual(msgs, expected) + try: + originalNick = conf.supybot.nick() + originalUser = conf.supybot.user() + originalPassword = conf.supybot.networks.test.password() + nick = 'nick' + conf.supybot.nick.setValue(nick) + user = 'user any user' + conf.supybot.user.setValue(user) + expected = [ircmsgs.nick(nick), ircmsgs.user('supybot', user)] + irc = irclib.Irc('test') + msgs = [irc.takeMsg()] + while msgs[-1] != None: + msgs.append(irc.takeMsg()) + msgs.pop() + self.assertEqual(msgs, expected) + password = 'password' + conf.supybot.networks.test.password.setValue(password) + irc = irclib.Irc('test') + msgs = [irc.takeMsg()] + while msgs[-1] != None: + msgs.append(irc.takeMsg()) + msgs.pop() + expected.insert(0, ircmsgs.password(password)) + self.assertEqual(msgs, expected) + finally: + conf.supybot.nick.setValue(nick) + conf.supybot.user.setValue(user) + conf.supybot.networks.test.password.setValue(password) + conf.supybot.nick.setValue(nick) # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: