1
0
forked from 0ad/0ad

Lobby Bot Optimizations and Upgrade to recent SleekXMPP/ejabberd versions.

Based on patch by scythetwirler, fix and improvements by user1, Dunedan
and myself.
Differential Revision: https://code.wildfiregames.com/D206
This was SVN commit r21718.
This commit is contained in:
Nicolas Auvray 2018-04-14 12:44:47 +00:00
parent d5f2ea9e78
commit 1d158a25ea
2 changed files with 108 additions and 58 deletions

View File

@ -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 @@ class LeaderboardList():
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 @@ class LeaderboardList():
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 @@ class LeaderboardList():
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 @@ class ReportManager():
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 @@ class EcheLOn(sleekxmpp.ClientXMPP):
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 @@ class EcheLOn(sleekxmpp.ClientXMPP):
"""
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 @@ class EcheLOn(sleekxmpp.ClientXMPP):
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 @@ class EcheLOn(sleekxmpp.ClientXMPP):
"""
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 @@ class EcheLOn(sleekxmpp.ClientXMPP):
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 @@ class EcheLOn(sleekxmpp.ClientXMPP):
"""
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 @@ if __name__ == '__main__':
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 @@ if __name__ == '__main__':
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")

View File

@ -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 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
# 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 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
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 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
# 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 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
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 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
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 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
"""
# 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 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
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 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
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 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
to all clients.
"""
games = self.gameList.getAllGames()
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.setPayload(stz)
if to == "":
for JID in list(self.nicks):
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'
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 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
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 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
"""
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 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
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 @@ if __name__ == '__main__':
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 @@ if __name__ == '__main__':
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")