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)
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()
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))
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))
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()
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())