diff --git a/constants/clientPackets.py b/constants/clientPackets.py index 6742d05..5a693ab 100644 --- a/constants/clientPackets.py +++ b/constants/clientPackets.py @@ -143,3 +143,12 @@ def transferHost(stream): def matchInvite(stream): return packetHelper.readPacketData(stream, [["userID", dataTypes.UINT32]]) + +def tournamentMatchInfoRequest(stream): + return packetHelper.readPacketData(stream, [["matchID", dataTypes.UINT32]]) + +def tournamentJoinMatchChannel(stream): + return packetHelper.readPacketData(stream, [["matchID", dataTypes.UINT32]]) + +def tournamentLeaveMatchChannel(stream): + return packetHelper.readPacketData(stream, [["matchID", dataTypes.UINT32]]) \ No newline at end of file diff --git a/constants/exceptions.py b/constants/exceptions.py index 0b41f28..d46170d 100644 --- a/constants/exceptions.py +++ b/constants/exceptions.py @@ -88,4 +88,7 @@ class loginLockedException(Exception): pass class unknownStreamException(Exception): + pass + +class userTournamentException(Exception): pass \ No newline at end of file diff --git a/constants/packetIDs.py b/constants/packetIDs.py index d3ca708..dfec010 100644 --- a/constants/packetIDs.py +++ b/constants/packetIDs.py @@ -74,7 +74,9 @@ server_channelInfoEnd = 89 client_matchChangePassword = 90 server_matchChangePassword = 91 server_silenceEnd = 92 -client_specialMatchInfoRequest = 93 server_userSilenced = 94 server_userPresenceBundle = 96 client_userPanelRequest = 97 +client_tournamentMatchInfoRequest = 93 +client_tournamentJoinMatchChannel = 108 +client_tournamentLeaveMatchChannel = 109 \ No newline at end of file diff --git a/constants/serverPackets.py b/constants/serverPackets.py index 69e000c..5cacefd 100644 --- a/constants/serverPackets.py +++ b/constants/serverPackets.py @@ -50,9 +50,10 @@ def mainMenuIcon(icon): def userSupporterGMT(supporter, GMT): result = 1 if supporter: - result += 4 + result |= userRanks.SUPPORTER if GMT: - result += 2 + result |= userRanks.BAT + result |= userRanks.TOURNAMENT_STAFF return packetHelper.buildPacket(packetIDs.server_supporterGMT, [[result, dataTypes.UINT32]]) def friendList(userID): @@ -78,9 +79,7 @@ def userLogout(userID): def userPanel(userID, force = False): # Connected and restricted check userToken = glob.tokens.getTokenFromUserID(userID) - if userToken is None: - return bytes() - if userToken.restricted == True and force == False: + if userToken is None or ((userToken.restricted) and not force): return bytes() # Get user data @@ -93,16 +92,17 @@ def userPanel(userID, force = False): # Get username color according to rank # Only admins and normal users are currently supported + userRank = 0 if username == "FokaBot": - userRank = userRanks.MOD + userRank |= userRanks.MOD elif userUtils.isInPrivilegeGroup(userID, "community manager"): - userRank = userRanks.MOD + userRank |= userRanks.MOD elif userUtils.isInPrivilegeGroup(userID, "developer"): - userRank = userRanks.ADMIN + userRank |= userRanks.ADMIN elif (userToken.privileges & privileges.USER_DONOR) > 0: - userRank = userRanks.SUPPORTER + userRank |= userRanks.SUPPORTER else: - userRank = userRanks.NORMAL + userRank |= userRanks.NORMAL return packetHelper.buildPacket(packetIDs.server_userPanel, [ @@ -120,10 +120,7 @@ def userPanel(userID, force = False): def userStats(userID, force = False): # Get userID's token from tokens list userToken = glob.tokens.getTokenFromUserID(userID) - - if userToken is None: - return bytes() - if (userToken.restricted == True or userToken.irc == True) and force == False: + if userToken is None or ((userToken.restricted or userToken.irc or userToken.tournament) and not force): return bytes() return packetHelper.buildPacket(packetIDs.server_userStats, @@ -204,6 +201,7 @@ def createMatch(matchID): match = glob.matches.matches[matchID] return packetHelper.buildPacket(packetIDs.server_newMatch, match.getMatchData()) +# TODO: Add match object argument to save some CPU def updateMatch(matchID): # Make sure the match exists if matchID not in glob.matches.matches: diff --git a/constants/userRanks.py b/constants/userRanks.py index c4c3fe8..cc372fe 100644 --- a/constants/userRanks.py +++ b/constants/userRanks.py @@ -1,8 +1,9 @@ """Bancho user ranks""" NORMAL = 0 PLAYER = 1 +BAT = 2 SUPPORTER = 4 MOD = 6 PEPPY = 8 ADMIN = 16 -TOURNAMENTSTAFF = 32 +TOURNAMENT_STAFF = 32 diff --git a/events/loginEvent.py b/events/loginEvent.py index 0d4cf5a..a6ca237 100644 --- a/events/loginEvent.py +++ b/events/loginEvent.py @@ -104,8 +104,10 @@ def handle(tornadoRequest): userUtils.logIP(userID, requestIP) # Delete old tokens for that user and generate a new one - glob.tokens.deleteOldTokens(userID) - responseToken = glob.tokens.addToken(userID, requestIP, timeOffset=timeOffset) + isTournament = "tourney" in osuVersion + if not isTournament: + glob.tokens.deleteOldTokens(userID) + responseToken = glob.tokens.addToken(userID, requestIP, timeOffset=timeOffset, tournament=isTournament) responseTokenString = responseToken.token # Check restricted mode (and eventually send message) @@ -205,7 +207,7 @@ def handle(tornadoRequest): if userUtils.getCountry(userID) == "XX": userUtils.setCountry(userID, countryLetters) - # Send to everyone our userpanel if we are not restricted + # Send to everyone our userpanel if we are not restricted or tournament if not responseToken.restricted: glob.streams.broadcast("main", serverPackets.userPanel(userID)) diff --git a/events/tournamentJoinMatchChannelEvent.py b/events/tournamentJoinMatchChannelEvent.py new file mode 100644 index 0000000..57ddc4e --- /dev/null +++ b/events/tournamentJoinMatchChannelEvent.py @@ -0,0 +1,11 @@ +from constants import clientPackets +from objects import glob +from helpers import chatHelper as chat + +def handle(userToken, packetData): + packetData = clientPackets.tournamentJoinMatchChannel(packetData) + matchID = packetData["matchID"] + if matchID not in glob.matches.matches: + return + userToken.matchID = matchID + chat.joinChannel(token=userToken, channel="#multi_{}".format(matchID)) \ No newline at end of file diff --git a/events/tournamentLeaveMatchChannelEvent.py b/events/tournamentLeaveMatchChannelEvent.py new file mode 100644 index 0000000..fd551fc --- /dev/null +++ b/events/tournamentLeaveMatchChannelEvent.py @@ -0,0 +1,11 @@ +from constants import clientPackets +from objects import glob +from helpers import chatHelper as chat + +def handle(userToken, packetData): + packetData = clientPackets.tournamentLeaveMatchChannel(packetData) + matchID = packetData["matchID"] + if matchID not in glob.matches.matches: + return + chat.partChannel(token=userToken, channel="#multi_{}".format(matchID)) + userToken.matchID = 0 \ No newline at end of file diff --git a/events/tournamentMatchInfoRequestEvent.py b/events/tournamentMatchInfoRequestEvent.py new file mode 100644 index 0000000..7be12fc --- /dev/null +++ b/events/tournamentMatchInfoRequestEvent.py @@ -0,0 +1,10 @@ +from constants import clientPackets +from constants import serverPackets +from objects import glob + +def handle(userToken, packetData): + packetData = clientPackets.tournamentMatchInfoRequest(packetData) + matchID = packetData["matchID"] + if matchID not in glob.matches.matches: + return + userToken.enqueue(serverPackets.updateMatch(matchID)) \ No newline at end of file diff --git a/handlers/mainHandler.py b/handlers/mainHandler.py index 9db07ca..420faf0 100644 --- a/handlers/mainHandler.py +++ b/handlers/mainHandler.py @@ -51,6 +51,9 @@ from events import startSpectatingEvent from events import stopSpectatingEvent from events import userPanelRequestEvent from events import userStatsRequestEvent +from events import tournamentMatchInfoRequestEvent +from events import tournamentJoinMatchChannelEvent +from events import tournamentLeaveMatchChannelEvent from helpers import packetHelper from objects import glob @@ -156,6 +159,10 @@ class handler(SentryMixin, requestsManager.asyncRequestHandler): packetIDs.client_matchFailed: handleEvent(matchFailedEvent), packetIDs.client_matchChangeTeam: handleEvent(matchChangeTeamEvent), packetIDs.client_invite: handleEvent(matchInviteEvent), + + packetIDs.client_tournamentMatchInfoRequest: handleEvent(tournamentMatchInfoRequestEvent), + packetIDs.client_tournamentJoinMatchChannel: handleEvent(tournamentJoinMatchChannelEvent), + packetIDs.client_tournamentLeaveMatchChannel: handleEvent(tournamentLeaveMatchChannelEvent), } # Packets processed if in restricted mode. diff --git a/helpers/chatHelper.py b/helpers/chatHelper.py index d9e7748..c9f913b 100644 --- a/helpers/chatHelper.py +++ b/helpers/chatHelper.py @@ -177,13 +177,17 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True): userID = token.userID username = token.username + # Make sure this is not a tournament client + if token.tournament: + raise exceptions.userTournamentException() + # Make sure the user is not in restricted mode if token.restricted: - raise exceptions.userRestrictedException + raise exceptions.userRestrictedException() # Make sure the user is not silenced if token.isSilenced(): - raise exceptions.userSilencedException + raise exceptions.userSilencedException() # Determine internal name if needed # (toclient is used clientwise for #multiplayer and #spectator channels) @@ -242,9 +246,13 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True): if recipientToken is None: raise exceptions.userNotFoundException + # Make sure the recipient is not a tournament client + if recipientToken.tournament: + raise exceptions.userTournamentException() + # Make sure the recipient is not restricted or we are FokaBot if recipientToken.restricted == True and fro.lower() != "fokabot": - raise exceptions.userRestrictedException + raise exceptions.userRestrictedException() # TODO: Make sure the recipient has not disabled PMs for non-friends or he's our friend @@ -290,6 +298,9 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True): except exceptions.userRestrictedException: log.warning("{} tried to send a message {}, but the recipient is in restricted mode".format(username, to)) return 404 + except exceptions.userTournamentException: + log.warning("{} tried to send a message {}, but the recipient is a tournament client".format(username, to)) + return 404 except exceptions.userNotFoundException: log.warning("User not connected to IRC/Bancho") return 401 diff --git a/objects/osuToken.py b/objects/osuToken.py index 752b5d6..f87b562 100644 --- a/objects/osuToken.py +++ b/objects/osuToken.py @@ -13,7 +13,7 @@ from objects import glob class token: - def __init__(self, userID, token_ = None, ip ="", irc = False, timeOffset = 0): + def __init__(self, userID, token_ = None, ip ="", irc = False, timeOffset = 0, tournament = False): """ Create a token object and set userID and token @@ -36,6 +36,7 @@ class token: self.timeOffset = timeOffset self.lock = threading.Lock() # Sync primitive self.streams = [] + self.tournament = tournament # Default variables self.spectators = [] diff --git a/objects/tokenList.py b/objects/tokenList.py index 1f6c149..c806514 100644 --- a/objects/tokenList.py +++ b/objects/tokenList.py @@ -20,7 +20,7 @@ class tokenList: """ self.tokens = {} - def addToken(self, userID, ip = "", irc = False, timeOffset=0): + def addToken(self, userID, ip = "", irc = False, timeOffset=0, tournament=False): """ Add a token object to tokens list @@ -28,7 +28,7 @@ class tokenList: irc -- if True, set this token as IRC client return -- token object """ - newToken = osuToken.token(userID, ip=ip, irc=irc, timeOffset=timeOffset) + newToken = osuToken.token(userID, ip=ip, irc=irc, timeOffset=timeOffset, tournament=tournament) self.tokens[newToken.token] = newToken return newToken