Index: ps/trunk/source/tools/XpartaMuPP/EcheLOn.py =================================================================== --- ps/trunk/source/tools/XpartaMuPP/EcheLOn.py +++ ps/trunk/source/tools/XpartaMuPP/EcheLOn.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -"""Copyright (C) 2016 Wildfire Games. +"""Copyright (C) 2018 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -74,7 +74,7 @@ the Player model, or the one that already exists in the database. """ - players = db.query(Player).filter_by(jid=str(JID)) + players = db.query(Player).filter(Player.jid.ilike(str(JID))) if not players.first(): player = Player(jid=str(JID), rating=-1) db.add(player) @@ -88,7 +88,7 @@ Returns the player that was removed, or None if that player didn't exist. """ - players = db.query(Player).filter_by(jid=JID) + players = db.query(Player).filter(Player.jid.ilike(str(JID))) player = players.first() if not player: return None @@ -253,6 +253,7 @@ for rank, player in enumerate(players): board[player.jid] = {'name': '@'.join(player.jid.split('@')[:-1]), 'rating': str(player.rating)} return board + def getRatingList(self, nicks): """ Returns a rating list of players @@ -368,6 +369,7 @@ return len(rawGameReport[key].split(","))-1 # Return -1 in case of failure. return -1 + ## Class for custom player stanza extension ## class PlayerXmppPlugin(ElementBase): name = 'query' @@ -447,6 +449,10 @@ self.sjid = sjid self.room = room self.nick = nick + self.ratingListCache = {} + self.ratingCacheReload = True + self.boardListCache = {} + self.boardCacheReload = True # Init leaderboard object self.leaderboard = LeaderboardList(room) @@ -528,12 +534,11 @@ """ Request lists. """ - if 'boardlist' in iq.plugins: + if 'boardlist' in iq.loaded_plugins: command = iq['boardlist']['command'] recipient = iq['boardlist']['recipient'] if command == 'getleaderboard': try: - self.leaderboard.getOrCreatePlayer(iq['from']) self.sendBoardList(iq['from'], recipient) except: traceback.print_exc() @@ -545,7 +550,7 @@ traceback.print_exc() else: logging.error("Failed to process boardlist request from %s" % iq['from'].bare) - elif 'profile' in iq.plugins: + elif 'profile' in iq.loaded_plugins: command = iq['profile']['command'] recipient = iq['profile']['recipient'] try: @@ -563,20 +568,23 @@ """ pass elif iq['type'] == 'set': - if 'gamereport' in iq.plugins: + if 'gamereport' in iq.loaded_plugins: """ Client is reporting end of game statistics """ try: + self.leaderboard.getOrCreatePlayer(iq['gamereport']['sender']) self.reportManager.addReport(iq['gamereport']['sender'], iq['gamereport']['game']) if self.leaderboard.getLastRatedMessage() != "": + self.ratingCacheReload = True + self.boardCacheReload = True self.send_message(mto=self.room, mbody=self.leaderboard.getLastRatedMessage(), mtype="groupchat", mnick=self.nick) self.sendRatingList(iq['from']) except: traceback.print_exc() logging.error("Failed to update game statistics for %s" % iq['from'].bare) - elif 'player' in iq.plugins: + elif 'player' in iq.loaded_plugins: player = iq['player']['online'] #try: self.leaderboard.getOrCreatePlayer(player) @@ -591,13 +599,17 @@ If no target is passed the boardlist is broadcasted to all clients. """ - ## Pull leaderboard data and add it to the stanza - board = self.leaderboard.getBoard() + ## See if we can squeak by with the cached version. + # Leaderboard cache is reloaded upon a new rated game being rated. + if self.boardCacheReload: + self.boardListCache = self.leaderboard.getBoard() + self.boardCacheReload = False + stz = BoardListXmppPlugin() iq = self.Iq() iq['type'] = 'result' - for i in board: - stz.addItem(board[i]['name'], board[i]['rating']) + for i in self.boardListCache: + stz.addItem(self.boardListCache[i]['name'], self.boardListCache[i]['rating']) stz.addCommand('boardlist') stz.addRecipient(recipient) iq.setPayload(stz) @@ -617,13 +629,24 @@ """ Send the rating list. """ - ## Pull rating list data and add it to the stanza - ratinglist = self.leaderboard.getRatingList(self.nicks) + ## Attempt to use the cache. + # Cache is invalidated when a new game is rated or a uncached player + # comes online. + if self.ratingCacheReload: + self.ratingListCache = self.leaderboard.getRatingList(self.nicks) + self.ratingCacheReload = False + else: + for JID in list(self.nicks): + if JID not in self.ratingListCache: + self.ratingListCache = self.leaderboard.getRatingList(self.nicks) + self.ratingCacheReload = False + break + stz = BoardListXmppPlugin() iq = self.Iq() iq['type'] = 'result' - for i in ratinglist: - stz.addItem(ratinglist[i]['name'], ratinglist[i]['rating']) + for i in self.ratingListCache: + stz.addItem(self.ratingListCache[i]['name'], self.ratingListCache[i]['rating']) stz.addCommand('ratinglist') iq.setPayload(stz) ## Check recipient exists @@ -740,6 +763,11 @@ action='store', dest='xroom', default="arena") + # ejabberd server options + optp.add_option('-s', '--server', help='address of the ejabberd server', + action='store', dest='xserver', + default="localhost") + opts, args = optp.parse_args() # Setup logging. @@ -754,7 +782,7 @@ xmpp.register_plugin('xep_0060') # PubSub xmpp.register_plugin('xep_0199') # XMPP Ping - if xmpp.connect(): + if xmpp.connect((opts.xserver, 5222)): xmpp.process(threaded=False) else: logging.error("Unable to connect") Index: ps/trunk/source/tools/XpartaMuPP/XpartaMuPP.py =================================================================== --- ps/trunk/source/tools/XpartaMuPP/XpartaMuPP.py +++ ps/trunk/source/tools/XpartaMuPP/XpartaMuPP.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -"""Copyright (C) 2016 Wildfire Games. +"""Copyright (C) 2018 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -178,6 +178,7 @@ # Store mapping of nicks and XmppIDs, attached via presence stanza self.nicks = {} + self.presences = {} # Obselete when XEP-0060 is implemented. self.lastLeft = "" @@ -212,6 +213,7 @@ self.add_event_handler("muc::%s::got_online" % self.room, self.muc_online) self.add_event_handler("muc::%s::got_offline" % self.room, self.muc_offline) self.add_event_handler("groupchat_message", self.muc_message) + self.add_event_handler("changed_status", self.presence_change) def start(self, event): """ @@ -233,6 +235,7 @@ # If it doesn't already exist, store player JID mapped to their nick. if str(presence['muc']['jid']) not in self.nicks: self.nicks[str(presence['muc']['jid'])] = presence['muc']['nick'] + self.presences[str(presence['muc']['jid'])] = "available" # Check the jid isn't already in the lobby. # Send Gamelist to new player. self.sendGameList(presence['muc']['jid']) @@ -254,6 +257,7 @@ self.lastLeft = str(presence['muc']['jid']) if str(presence['muc']['jid']) in self.nicks: del self.nicks[str(presence['muc']['jid'])] + del self.presences[str(presence['muc']['jid'])] if presence['muc']['nick'] == self.ratingsBot: self.ratingsBotWarned = False @@ -266,6 +270,21 @@ mbody="I am the administrative bot in this lobby and cannot participate in any games.", mtype='groupchat') + def presence_change(self, presence): + """ + Processes presence change + """ + prefix = "%s/" % self.room + nick = str(presence['from']).replace(prefix, "") + for JID in self.nicks: + if self.nicks[JID] == nick: + if self.presences[JID] == 'dnd' and (str(presence['type']) == "available" or str(presence['type']) == "away"): + self.sendGameList(JID) + self.relayBoardListRequest(JID) + self.presences[JID] = str(presence['type']) + break + + def iqhandler(self, iq): """ Handle the custom stanzas @@ -280,36 +299,36 @@ """ # Send lists/register on leaderboard; depreciated once muc_online # can send lists/register automatically on joining the room. - if 'boardlist' in iq.plugins: + if 'boardlist' in iq.loaded_plugins: command = iq['boardlist']['command'] try: self.relayBoardListRequest(iq['from']) except: traceback.print_exc() logging.error("Failed to process leaderboardlist request from %s" % iq['from'].bare) - elif 'profile' in iq.plugins: + elif 'profile' in iq.loaded_plugins: command = iq['profile']['command'] try: self.relayProfileRequest(iq['from'], command) except: - pass # TODO needed? + pass else: logging.error("Unknown 'get' type stanza request from %s" % iq['from'].bare) elif iq['type'] == 'result': """ Iq successfully received """ - if 'boardlist' in iq.plugins: + if 'boardlist' in iq.loaded_plugins: recipient = iq['boardlist']['recipient'] self.relayBoardList(iq['boardlist'], recipient) - elif 'profile' in iq.plugins: + elif 'profile' in iq.loaded_plugins: recipient = iq['profile']['recipient'] player = iq['profile']['command'] self.relayProfile(iq['profile'], player, recipient) else: - pass # TODO error/warn? + pass elif iq['type'] == 'set': - if 'gamelist' in iq.plugins: + if 'gamelist' in iq.loaded_plugins: """ Register-update / unregister a game """ @@ -317,8 +336,9 @@ if command == 'register': # Add game try: - self.gameList.addGame(iq['from'], iq['gamelist']['game']) - self.sendGameList() + if iq['from'] in self.nicks: + self.gameList.addGame(iq['from'], iq['gamelist']['game']) + self.sendGameList() except: traceback.print_exc() logging.error("Failed to process game registration data") @@ -338,10 +358,16 @@ self.sendGameList() except: traceback.print_exc() - logging.error("Failed to process changestate data") + logging.error("Failed to process changestate data. Trying to add game") + try: + if iq['from'] in self.nicks: + self.gameList.addGame(iq['from'], iq['gamelist']['game']) + self.sendGameList() + except: + pass else: logging.error("Failed to process command '%s' received from %s" % command, iq['from'].bare) - elif 'gamereport' in iq.plugins: + elif 'gamereport' in iq.loaded_plugins: """ Client is reporting end of game statistics """ @@ -360,20 +386,23 @@ to all clients. """ games = self.gameList.getAllGames() - if to == "": - for JID in list(self.nicks): - stz = GameListXmppPlugin() + + stz = GameListXmppPlugin() - ## Pull games and add each to the stanza - for JIDs in games: - g = games[JIDs] - stz.addGame(g) + ## Pull games and add each to the stanza + for JIDs in games: + g = games[JIDs] + stz.addGame(g) - ## Set additional IQ attributes - iq = self.Iq() - iq['type'] = 'result' + ## Set additional IQ attributes + iq = self.Iq() + iq['type'] = 'result' + iq.setPayload(stz) + if to == "": + for JID in list(self.presences): + if self.presences[JID] != "available" and self.presences[JID] != "away": + continue iq['to'] = JID - iq.setPayload(stz) ## Try sending the stanza try: @@ -385,18 +414,7 @@ if str(to) not in self.nicks: logging.error("No player with the XmPP ID '%s' known to send gamelist to." % str(to)) return - stz = GameListXmppPlugin() - - ## Pull games and add each to the stanza - for JIDs in games: - g = games[JIDs] - stz.addGame(g) - - ## Set additional IQ attributes - iq = self.Iq() - iq['type'] = 'result' iq['to'] = to - iq.setPayload(stz) ## Try sending the stanza try: @@ -520,14 +538,13 @@ """ iq = self.Iq() iq['type'] = 'result' - """for i in board: - stz.addItem(board[i]['name'], board[i]['rating']) - stz.addCommand('boardlist')""" iq.setPayload(boardList) ## Check recipient exists if to == "": # Rating List - for JID in list(self.nicks): + for JID in list(self.presences): + if self.presences[JID] != "available" and self.presences[JID] != "away": + continue ## Set additional IQ attributes iq['to'] = JID ## Try sending the stanza @@ -536,7 +553,7 @@ except: logging.error("Failed to send rating list") else: - # Leaderboard + # Leaderboard or targeted rating list if str(to) not in self.nicks: logging.error("No player with the XmPP ID '%s' known to send boardlist to" % str(to)) return @@ -618,6 +635,11 @@ action='store', dest='xratingsbot', default="disabled") + # ejabberd server options + optp.add_option('-s', '--server', help='address of the ejabberd server', + action='store', dest='xserver', + default="localhost") + opts, args = optp.parse_args() # Setup logging. @@ -632,7 +654,7 @@ xmpp.register_plugin('xep_0060') # PubSub xmpp.register_plugin('xep_0199') # XMPP Ping - if xmpp.connect(): + if xmpp.connect((opts.xserver, 5222)): xmpp.process(threaded=False) else: logging.error("Unable to connect")