Ejemplo n.º 1
0
    def tryjoin(self):
        """tryjoin() -> None

        The 'tryjoin' event handler. Begins an attempt to join the MUC.

        This is fired off by the 'joining' event, and repeated if the MUC
        join does not succeed.
        """
        
        self.mucnick = interface.JID(jid=self.muc)

        # Our nickname is based on the basenick the referee gave us. But if
        # a previous attempt failed, we jigger it, to avoid repeating
        # collisions.
        nick = self.basenick
        self.mucnickcount += 1
        if (self.mucnickcount > 1):
            nick = nick + '-' + str(self.mucnickcount)
        self.mucnick.setresource(nick)
        
        self.log.info('joining muc: %s as %s', unicode(self.muc), unicode(self.mucnick))

        msg = interface.Node('presence',
            attrs={ 'to':unicode(self.mucnick) })
        msg.setchild('x', namespace=interface.NS_MUC)
        # We're not using the Zymb PresenceService, so we add the capabilities
        # tag manually.
        self.cappresencehook(msg)

        self.conn.send(msg, addid=False)
Ejemplo n.º 2
0
    def handlepresence(self, msg):
        """handlepresence(msg) -> <stanza outcome>

        Handle a Jabber presence stanza. If it signals a failure to join the
        MUC, perform 'tryjoin' again. If it comes from the actor's MUC room,
        it is passed along to handlemucpresence(). If it is the actor's own
        JID being unavailable, consider the MUC to be dead. Otherwise, it is
        ignored.
        """
        
        typestr = msg.getattr('type', '')
                
        fromstr = msg.getattr('from')
        if (fromstr):
            jid = interface.JID(fromstr)
        else:
            jid = None

        if (not (jid and self.jid.barematch(jid))):
            if (self.log.isEnabledFor(logging.DEBUG)):
                self.log.debug('received presence:\n%s', msg.serialize(True))
            else:
                self.log.info('received presence \'%s\' from %s', typestr, unicode(jid))

        if (self.mucnick == jid and typestr == 'unavailable'):
            self.log.warning('MUC has closed; shutting down')
            self.queueaction(self.stop)
            raise interface.StanzaHandled()

        if (typestr == 'error'
            and msg.getchild('x', namespace=interface.NS_MUC)
            and self.state == 'joining'):
            errnod = msg.getchild('error')
            if (errnod
                and (errnod.getattr('code') == '409'
                    or errnod.getchild('conflict'))
                and self.mucnickcount < 15):
                # nickname conflict
                self.log.info('mucnick %s failed; retrying',
                    unicode(self.mucnick))
                self.perform('tryjoin')
                raise interface.StanzaHandled()
            # else, simple join failure
            self.log.info('unable to join MUC; shutting down')
            self.queueaction(self.stop)
            raise interface.StanzaHandled()

        if (jid
            and jid.getnode() == self.refereejid.getresource()
            and jid.getdomain() == self.muc.getdomain()):
            self.handlemucpresence(typestr, jid.getresource(), msg)
            
        raise interface.StanzaHandled()
Ejemplo n.º 3
0
    def newbot(self, sender, *args):
        """newbot(sender, uri, tablejid) -> <RPC outcome>

        Create a new bot, and have it join the given table. The RPC
        response will be the bot's full JID, but that doesn't happen in this
        function; see the botready() callback.
        """
        
        if (len(args) != 2):
            raise rpc.RPCFault(604, 'new_bot takes two arguments')
        if (not type(args[0]) in (str, unicode)):
            raise rpc.RPCFault(605, 'new_bot: first argument must be string')
        if (not type(args[1]) in (str, unicode)):
            raise rpc.RPCFault(605, 'new_bot: second argument must be string')
        uri = args[0]
        tablejidstr = args[1]
        tablejid = interface.JID(tablejidstr)

        if (self.state != 'ready'):
            raise rpc.RPCFault(609, 'factory is not yet ready')

        if (not self.online):
            raise volent.FailureToken('volity.offline')

        found = False
        for botclass in self.botclasses:
            if (botclass.boturi == uri):
                found = True
                break
        if (not found):
            raise volent.FailureToken('volity.bot_not_available')
        
        self.log.info('new bot requested by %s (%s)...', unicode(sender),
            botclass.boturi)

        botresource = 'bot_' + str(os.getpid()) + '_' + str(self.uniquestamp())
        
        # The player-visible name of the bot shouldn't conflict with existing
        # MUC nicknames. We will do collision detection when we join the MUC,
        # but some MUC servers get crashy when you do that (in cases which
        # we haven't diagnosed yet). So we make a rough attempt to start
        # with a never-before-used name.
        botnick = 'Bot ' + str(self.botcounter)
        self.botcounter += 1

        try:
            act = actor.Actor(self, sender, self.jid, self.password,
                tablejid, botresource, botnick, botclass)
        except Exception, ex:
            self.log.error('Unable to create bot',
                exc_info=True)
            raise rpc.RPCFault(608,
                'unable to create bot: ' + str(ex))
Ejemplo n.º 4
0
    def newtable(self, sender, *args):
        """newtable(sender, *args) -> <RPC outcome>

        Create a new table and Referee. The RPC response will be the
        table JID, but that doesn't happen in this function; see the
        refereeready() callback.
        """

        if (len(args) != 0):
            raise rpc.RPCFault(604, 'new_table takes no arguments')

        if (self.state != 'ready'):
            raise rpc.RPCFault(609, 'parlor is not yet ready')

        if (not self.online):
            raise volent.FailureToken('volity.offline')

        self.log.info('new table requested by %s...', unicode(sender))

        # see unique-nick discussion, bug 1308207
        # assumes resource didn't change
        refresource = 'ref_' + str(os.getpid()) + '_' + str(self.uniquestamp())
        muc = interface.JID(self.muchost)
        muc.setnode(refresource)

        # Create the actual Referee object.

        try:
            refclass = self.gameclass.refereeclass
            if (refclass):
                if (not issubclass(refclass, referee.Referee)):
                    raise Exception(
                        'refereeclass must be a subclass of Referee')
            else:
                refclass = referee.Referee
            ref = refclass(self, self.jid, self.password, refresource, muc)
        except Exception, ex:
            self.log.error('Unable to create referee', exc_info=True)
            raise rpc.RPCFault(608, 'unable to create referee: ' + str(ex))
Ejemplo n.º 5
0
    def handlepresence(self, msg):
        """handlepresence(msg) -> <stanza outcome>

        Handle a Jabber presence stanza. The Parlor does not react to presence,
        so this just raises StanzaHandled to end processing for the stanza.
        """

        typestr = msg.getattr('type', '')

        fromstr = msg.getattr('from')
        if (fromstr):
            jid = interface.JID(fromstr)
        else:
            jid = None

        if (not (jid and self.jid.barematch(jid))):
            if (self.log.isEnabledFor(logging.DEBUG)):
                self.log.debug('received presence:\n%s', msg.serialize(True))
            else:
                self.log.info('received presence \'%s\' from %s', typestr,
                              unicode(jid))
        raise interface.StanzaHandled()
Ejemplo n.º 6
0
    def __init__(self, config):
        self.jabbersecurity = config.get('jabber-security')
        volent.VolEntity.__init__(self,
                                  config.get('jid'),
                                  config.get('password'),
                                  config.get('jid-resource'),
                                  config.get('host'),
                                  secure=self.jabbersecurity)

        # Set the default shutdown conditions

        self.shutdowngraceful = True
        self.shutdownrestart = True
        self.shutdownrestartdelay = True
        self.canrestart = config.has_key('restart-script')
        self.restartfunc = config.get('restart-func-')

        # Locate the game class.

        ls = config.get('game').split('.')
        if (len(ls) < 2):
            raise ValueError('gameclass must be of the form module.gameclass')
        classname = ls[-1]
        modpath = '.'.join(ls[:-1])
        mod = __import__(modpath, globals(), locals(), [classname])
        gameclass = getattr(mod, classname)
        self.gameclass = gameclass

        if (gameclass == game.Game or (type(gameclass) != types.ClassType)
                or (not issubclass(gameclass, game.Game))):
            raise ValueError(
                'gameclass must be a subclass of volity.game.Game')

        if (not gameclass.gamename):
            raise TypeError('gameclass does not define class.gamename')
        if (not gameclass.ruleseturi):
            raise TypeError('gameclass does not define class.ruleseturi')

        # Locate the bot classes (if there are any).
        self.botclasses = []

        botls = config.getall('bot')
        for botclassname in botls:
            ls = botclassname.split('.')
            if (len(ls) < 2):
                raise ValueError(
                    'botclass must be of the form module.botclass')
            classname = ls[-1]
            modpath = '.'.join(ls[:-1])
            mod = __import__(modpath, globals(), locals(), [classname])
            botclass = getattr(mod, classname)
            self.botclasses.append(botclass)

            if (botclass == bot.Bot or (type(botclass) != types.ClassType)
                    or (not issubclass(botclass, bot.Bot))):
                raise ValueError(
                    'botclass must be a subclass of volity.bot.Bot')

            if (botclass.gameclass == None
                    or (type(botclass.gameclass) != types.ClassType)
                    or (not issubclass(self.gameclass, botclass.gameclass))):
                raise ValueError('botclass does not play ' +
                                 config.get('game'))

            if (not botclass.boturi):
                raise TypeError('botclass does not define class.boturi')

            if (botclass.boturi in [bc.boturi for bc in self.botclasses[:-1]]):
                raise TypeError('class.boturi used more than once')

        self.botfactories = []
        for jidstr in config.getall('bot-factory'):
            self.botfactories.append(interface.JID(jidstr))

        bookjidstr = config.get('bookkeeper', '[email protected]/volity')
        self.bookkeeperjid = interface.JID(bookjidstr)
        if (not self.bookkeeperjid.getresource()):
            self.bookkeeperjid.setresource('volity')

        # Set the administrative JID (if present)

        self.adminjids = []
        if (config.get('admin')):
            val = config.get('admin')
            ls = [interface.JID(subval.strip()) for subval in val.split(',')]
            self.adminjids = ls

        # Various Jabber handlers.

        self.addhandler('end', self.endwork)
        self.conn.adddispatcher(self.handlepresence, name='presence')
        self.conn.adddispatcher(self.handlemessage, name='message')

        # Set up internal state.

        val = config.get('entity-name')
        if (not val):
            val = gameclass.gamename
        self.gamename = val
        self.log.warning('Game parlor running: %s', self.gamename)

        self.referees = {}
        self.muchost = interface.JID(
            config.get('muchost', 'conference.volity.net'))
        self.online = True
        self.startuptime = time.time()
        self.activitytime = None
        self.refereesstarted = 0
        self.visibility = config.getbool('visible', True)

        self.actors = {}

        # Set up the disco service

        disco = self.conn.getservice('discoservice')

        info = disco.addinfo()
        info.addidentity('volity', 'parlor', self.gamename)
        info.addfeature(interface.NS_CAPS)
        info2 = disco.addinfo(volent.VOLITY_CAPS_URI + '#' +
                              volent.volityversion)
        info2.addidentity('volity', 'parlor', self.gamename)
        info2.addfeature(interface.NS_CAPS)

        form = jabber.dataform.DataForm()
        self.dataform = form
        val = config.get('entity-desc')
        if (not val):
            val = gameclass.gamedescription
        form.addfield('description', val)
        form.addfield('ruleset', gameclass.ruleseturi)
        form.addfield('volity-role', self.volityrole)
        form.addfield('ruleset-version', gameclass.rulesetversion)
        val = gameclass.websiteurl
        if (not val):
            val = gameclass.ruleseturi
        form.addfield('website', val)
        if (config.get('contact-email')):
            form.addfield('contact-email', config.get('contact-email'))
        if (config.get('contact-jid')):
            form.addfield('contact-jid', config.get('contact-jid'))
        form.addfield('volity-version', volent.volityversion)
        if (self.visibility):
            val = '1'
        else:
            val = '0'
        form.addfield('visible', val)
        info.setextendedinfo(form)
        info2.setextendedinfo(form)

        infocap = jabber.disco.DiscoInfo()
        infocap.addfeature(volent.VOLITY_CAPS_URI + '#' + self.volityrole)
        disco.addinfo(volent.VOLITY_CAPS_URI + '#' + self.volityrole, infocap)

        # assumes resource didn't change
        items = disco.additems()
        items.additem(self.jid, node='ruleset', name='Ruleset URI')
        items.additem(self.jid,
                      node='open_games',
                      name='Open games at this parlor')
        items.additem(self.jid,
                      node='bots',
                      name='Bots available from this parlor')

        items = disco.additems('ruleset')
        items.additem(unicode(self.bookkeeperjid),
                      node=gameclass.ruleseturi,
                      name='Ruleset information (%s)' % gameclass.ruleseturi)

        items = disco.additems('open_games', self.listopengames)
        items = disco.additems('bots', self.listbots)

        # Set up the RPC service

        self.rpccli = self.conn.getservice('rpcclience')

        rpcserv = self.conn.getservice('rpcservice')
        assert isinstance(rpcserv.getopset(), volent.ClientRPCWrapperOpset)
        ops = rpcserv.getopset().getopset()
        dic = ops.getdict()
        dic['volity'] = ParlorVolityOpset(self)
        dic['admin'] = ParlorAdminOpset(self)

        # Set up a simple roster-handler to accept subscription requests.

        self.conn.adddispatcher(self.handlerosterquery, name='iq', type='set')

        # Set up the keepalive service

        keepaliveflag = False
        if (config.get('keepalive')):
            keepaliveflag = True
        keepaliveinterval = None
        if (config.get('keepalive-interval')):
            keepaliveinterval = int(config.get('keepalive-interval'))
        if (keepaliveflag or keepaliveinterval):
            if (keepaliveinterval):
                serv = jabber.keepalive.KeepAliveService(keepaliveinterval)
            else:
                serv = jabber.keepalive.KeepAliveService()
            self.conn.addservice(serv)
            self.addhandler('ready', serv.start)
            self.log.warning(
                'sending keepalive messages to self at interval of %d seconds',
                serv.getinterval())