Search code examples
pythontwisteddeferred

Twisted deferred variable from another source


I'm running an IRC bot, I finally figured out how to whois to properly get auth of an user. I'm now looking to implement this check to referrence against my database instead of relying on their username.

I have a command

def privmsg(self, user, channel, msg):
    if msg.startswith(".join"):
        # sends whois signal
        irc.IRCClient.whois(self, user, None)

I need to get variable from a function that gets called by outcoming source (IRC server), therefore I can't just AddCallback

        auth = ???
        player = db.getplayer(auth)

I'm receiving auth through

def irc_330(self, prefix, params):
    auth = params[2]

I can't seem to figure out how exactly to send the variable back to the first function, or even how to wait for that signal to come back. My first function will pass anything irc_330() does before it even starts.

The only solution I can think of is create a callback function that will wait for a call of function inside that to then return what I need. But that doesn't seem right at all.


Solution

  • The problem that you're running into here is that IRCClient's API is rather crummy (sorry about that!). Specifically, it has APIs like .whois() which should return a Deferred, but don't.

    You can fix this problem by making your own version of these APIs which do, in fact, return a Deferred.

    The general idea is that IRC requests and responses don't have request-IDs, so they are always answered in order. That means you need to establish a queue of first-in/first-out requests so you can match them to replies as the replies arrive. Also, you need to match the Deferred firing to the end of the query (in your case, WHOIS, and therefore, RPL_ENDOFWHOIS), since IRC servers may or may not send a particular field as part of the response.

    Here's an example implementation of just that:

    from collections import deque
    from twisted.internet.defer import Deferred
    from twisted.words.protocols.irc import IRCClient
    
    class NoAccount(Exception):
        "No account found."
    
    class MyClient(IRCClient, object):
    
        def __init__(self):
            self._whoisQueue = deque()
    
        def deferredWhois(self, nick):
            result = Deferred()
            self._whoisQueue.append((result, nick))
            self.whois(nick)
            return result
    
        def irc_330(self, prefix, params):
            self._currentActiveNick = params[2]
    
        def irc_RPL_ENDOFWHOIS(self, prefix, params):
            deferredToFire, who = self._whoisQueue.popleft()
            currentActiveNick = self._currentActiveNick
            self._currentActiveNick = None
    
            if currentActiveNick is None:
                deferredToFire.errback(NoAccount(who))
            else:
                deferredToFire.callback(currentActiveNick)
    

    This makes the deferredWhois method return a Deferred that fires with the account name of the given nick.

    Hope this helps!