Merged with upstream

This commit is contained in:
Sunpy 2018-04-08 21:20:26 +02:00
commit 2cd69a9a63
11 changed files with 114 additions and 44 deletions

1
.gitignore vendored
View File

@ -7,3 +7,4 @@ filters.txt
redistest.py redistest.py
*.c *.c
*.so *.so
.pyenv

View File

@ -40,10 +40,10 @@ def instantRestart(fro, chan, message):
return False return False
def faq(fro, chan, message): def faq(fro, chan, message):
if message[0] in glob.conf.extra["faq"]: key = message[0].lower()
return glob.conf.extra["faq"][message[0]] if key not in glob.conf.extra["faq"]:
return False
return False return glob.conf.extra["faq"][key]
def roll(fro, chan, message): def roll(fro, chan, message):
maxPoints = 100 maxPoints = 100
@ -58,8 +58,8 @@ def roll(fro, chan, message):
# return random.choice(["yes", "no", "maybe"]) # return random.choice(["yes", "no", "maybe"])
def alert(fro, chan, message): def alert(fro, chan, message):
msg = ' '.join(message[:]) msg = ' '.join(message[:]).strip()
if not msg.strip(): if not msg:
return False return False
glob.streams.broadcast("main", serverPackets.notification(msg)) glob.streams.broadcast("main", serverPackets.notification(msg))
return False return False
@ -68,8 +68,8 @@ def alertUser(fro, chan, message):
target = message[0].lower() target = message[0].lower()
targetToken = glob.tokens.getTokenFromUsername(userUtils.safeUsername(target), safe=True) targetToken = glob.tokens.getTokenFromUsername(userUtils.safeUsername(target), safe=True)
if targetToken is not None: if targetToken is not None:
msg = ' '.join(message[1:]) msg = ' '.join(message[1:]).strip()
if not msg.strip(): if not msg:
return False return False
targetToken.enqueue(serverPackets.notification(msg)) targetToken.enqueue(serverPackets.notification(msg))
return False return False
@ -137,12 +137,15 @@ def fokabotReconnect(fro, chan, message):
return False return False
def silence(fro, chan, message): def silence(fro, chan, message):
for i in message: message = [x.lower() for x in message]
i = i.lower()
target = message[0] target = message[0]
amount = message[1] amount = message[1]
unit = message[2] unit = message[2]
reason = ' '.join(message[3:]) reason = ' '.join(message[3:]).strip()
if not reason:
return "Please provide a valid reason."
if not amount.isdigit():
return "The amount must be a number."
# Get target user ID # Get target user ID
targetUserID = userUtils.getIDSafe(target) targetUserID = userUtils.getIDSafe(target)
@ -758,7 +761,10 @@ def multiplayer(fro, chan, message):
def mpMake(): def mpMake():
if len(message) < 2: if len(message) < 2:
raise exceptions.invalidArgumentsException("Wrong syntax: !mp make <name>") raise exceptions.invalidArgumentsException("Wrong syntax: !mp make <name>")
matchID = glob.matches.createMatch(" ".join(message[1:]), generalUtils.stringMd5(generalUtils.randomString(32)), 0, "Tournament", "", 0, -1, isTourney=True) matchName = " ".join(message[1:]).strip()
if not matchName:
raise exceptions.invalidArgumentsException("Match name must not be empty!")
matchID = glob.matches.createMatch(matchName, generalUtils.stringMd5(generalUtils.randomString(32)), 0, "Tournament", "", 0, -1, isTourney=True)
glob.matches.matches[matchID].sendUpdates() glob.matches.matches[matchID].sendUpdates()
return "Tourney match #{} created!".format(matchID) return "Tourney match #{} created!".format(matchID)
@ -812,7 +818,9 @@ def multiplayer(fro, chan, message):
def mpHost(): def mpHost():
if len(message) < 2: if len(message) < 2:
raise exceptions.invalidArgumentsException("Wrong syntax: !mp host <username>") raise exceptions.invalidArgumentsException("Wrong syntax: !mp host <username>")
username = message[1] username = message[1].strip()
if not username:
raise exceptions.invalidArgumentsException("Please provide a username")
userID = userUtils.getIDSafe(username) userID = userUtils.getIDSafe(username)
if userID is None: if userID is None:
raise exceptions.userNotFoundException("No such user") raise exceptions.userNotFoundException("No such user")
@ -877,7 +885,9 @@ def multiplayer(fro, chan, message):
def mpInvite(): def mpInvite():
if len(message) < 2: if len(message) < 2:
raise exceptions.invalidArgumentsException("Wrong syntax: !mp invite <username>") raise exceptions.invalidArgumentsException("Wrong syntax: !mp invite <username>")
username = message[1] username = message[1].strip()
if not username:
raise exceptions.invalidArgumentsException("Please provide a username")
userID = userUtils.getIDSafe(username) userID = userUtils.getIDSafe(username)
if userID is None: if userID is None:
raise exceptions.userNotFoundException("No such user") raise exceptions.userNotFoundException("No such user")
@ -944,7 +954,9 @@ def multiplayer(fro, chan, message):
def mpKick(): def mpKick():
if len(message) < 2: if len(message) < 2:
raise exceptions.invalidArgumentsException("Wrong syntax: !mp kick <username>") raise exceptions.invalidArgumentsException("Wrong syntax: !mp kick <username>")
username = message[1] username = message[1].strip()
if not username:
raise exceptions.invalidArgumentsException("Please provide a username")
userID = userUtils.getIDSafe(username) userID = userUtils.getIDSafe(username)
if userID is None: if userID is None:
raise exceptions.userNotFoundException("No such user") raise exceptions.userNotFoundException("No such user")
@ -957,7 +969,7 @@ def multiplayer(fro, chan, message):
return "{} has been kicked from the match.".format(username) return "{} has been kicked from the match.".format(username)
def mpPassword(): def mpPassword():
password = "" if len(message) < 2 else message[1] password = "" if len(message) < 2 or not message[1].strip() else message[1]
_match = glob.matches.matches[getMatchIDFromChannel(chan)] _match = glob.matches.matches[getMatchIDFromChannel(chan)]
_match.changePassword(password) _match.changePassword(password)
return "Match password has been changed!" return "Match password has been changed!"
@ -1001,7 +1013,9 @@ def multiplayer(fro, chan, message):
def mpTeam(): def mpTeam():
if len(message) < 3: if len(message) < 3:
raise exceptions.invalidArgumentsException("Wrong syntax: !mp team <username> <colour>") raise exceptions.invalidArgumentsException("Wrong syntax: !mp team <username> <colour>")
username = message[1] username = message[1].strip()
if not username:
raise exceptions.invalidArgumentsException("Please provide a username")
colour = message[2].lower().strip() colour = message[2].lower().strip()
if colour not in ["red", "blue"]: if colour not in ["red", "blue"]:
raise exceptions.invalidArgumentsException("Team colour must be red or blue") raise exceptions.invalidArgumentsException("Team colour must be red or blue")
@ -1068,7 +1082,7 @@ def multiplayer(fro, chan, message):
if requestedSubcommand not in subcommands: if requestedSubcommand not in subcommands:
raise exceptions.invalidArgumentsException("Invalid subcommand") raise exceptions.invalidArgumentsException("Invalid subcommand")
return subcommands[requestedSubcommand]() return subcommands[requestedSubcommand]()
except (exceptions.invalidArgumentsException, exceptions.userNotFoundException) as e: except (exceptions.invalidArgumentsException, exceptions.userNotFoundException, exceptions.invalidUserException) as e:
return str(e) return str(e)
except exceptions.wrongChannelException: except exceptions.wrongChannelException:
return "This command only works in multiplayer chat channels" return "This command only works in multiplayer chat channels"
@ -1080,7 +1094,9 @@ def multiplayer(fro, chan, message):
def switchServer(fro, chan, message): def switchServer(fro, chan, message):
# Get target user ID # Get target user ID
target = message[0] target = message[0]
newServer = message[1] newServer = message[1].strip()
if not newServer:
return "Invalid server IP"
targetUserID = userUtils.getIDSafe(target) targetUserID = userUtils.getIDSafe(target)
userID = userUtils.getID(fro) userID = userUtils.getID(fro)
@ -1098,7 +1114,9 @@ def switchServer(fro, chan, message):
def rtx(fro, chan, message): def rtx(fro, chan, message):
target = message[0] target = message[0]
message = " ".join(message[1:]) message = " ".join(message[1:]).strip()
if not message:
return "Invalid message"
targetUserID = userUtils.getIDSafe(target) targetUserID = userUtils.getIDSafe(target)
if not targetUserID: if not targetUserID:
return "{}: user not found".format(target) return "{}: user not found".format(target)

View File

@ -1,5 +1,5 @@
from common.log import logUtils as log from common.log import logUtils as log
from constants import clientPackets from constants import clientPackets, serverPackets
from constants import exceptions from constants import exceptions
from objects import glob from objects import glob
@ -12,9 +12,14 @@ def handle(userToken, packetData):
# Read packet data # Read packet data
packetData = clientPackets.createMatch(packetData) packetData = clientPackets.createMatch(packetData)
# Make sure the name is valid
matchName = packetData["matchName"].strip()
if not matchName:
raise exceptions.matchCreateError()
# Create a match object # Create a match object
# TODO: Player number check # TODO: Player number check
matchID = glob.matches.createMatch(packetData["matchName"], packetData["matchPassword"], packetData["beatmapID"], packetData["beatmapName"], packetData["beatmapMD5"], packetData["gameMode"], userID) matchID = glob.matches.createMatch(matchName, packetData["matchPassword"].strip(), packetData["beatmapID"], packetData["beatmapName"], packetData["beatmapMD5"], packetData["gameMode"], userID)
# Make sure the match has been created # Make sure the match has been created
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
@ -30,3 +35,4 @@ def handle(userToken, packetData):
match.changePassword(packetData["matchPassword"]) match.changePassword(packetData["matchPassword"])
except exceptions.matchCreateError: except exceptions.matchCreateError:
log.error("Error while creating match!") log.error("Error while creating match!")
userToken.enqueue(serverPackets.matchJoinFail())

View File

@ -79,7 +79,7 @@ def partChannel(userID = 0, channel = "", token = None, toIRC = True, kick = Fal
token = glob.tokens.getTokenFromUserID(userID) token = glob.tokens.getTokenFromUserID(userID)
# Make sure the token exists # Make sure the token exists
if token is None: if token is None:
raise exceptions.userNotFoundException raise exceptions.userNotFoundException()
else: else:
token = token token = token
@ -157,7 +157,7 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
if token is None: if token is None:
token = glob.tokens.getTokenFromUsername(fro) token = glob.tokens.getTokenFromUsername(fro)
if token is None: if token is None:
raise exceptions.userNotFoundException raise exceptions.userNotFoundException()
else: else:
# token object alredy passed, get its string and its username (fro) # token object alredy passed, get its string and its username (fro)
fro = token.username fro = token.username
@ -194,6 +194,11 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
toClient = "#spectator" toClient = "#spectator"
elif to.startswith("#multi_"): elif to.startswith("#multi_"):
toClient = "#multiplayer" toClient = "#multiplayer"
# Make sure the message is valid
if not message.strip():
raise exceptions.invalidArgumentsException()
# Truncate message if > 2048 characters # Truncate message if > 2048 characters
message = message[:2048]+"..." if len(message) > 2048 else message message = message[:2048]+"..." if len(message) > 2048 else message
@ -297,6 +302,9 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
except exceptions.userNotFoundException: except exceptions.userNotFoundException:
log.warning("User not connected to IRC/Bancho") log.warning("User not connected to IRC/Bancho")
return 401 return 401
except exceptions.invalidArgumentsException:
log.warning("{} tried to send an invalid message to {}".format(token.username, to))
return 404
""" IRC-Bancho Connect/Disconnect/Join/Part interfaces""" """ IRC-Bancho Connect/Disconnect/Join/Part interfaces"""

View File

@ -28,7 +28,7 @@ def printServerStartHeader(asciiArt=True):
printColored("> Welcome to pep.py osu!bancho server v{}".format(glob.VERSION), bcolors.GREEN) printColored("> Welcome to pep.py osu!bancho server v{}".format(glob.VERSION), bcolors.GREEN)
printColored("> Made by the Ripple team", bcolors.GREEN) printColored("> Made by the Ripple team", bcolors.GREEN)
printColored("> {}https://git.zxq.co/ripple/pep.py".format(bcolors.UNDERLINE), bcolors.GREEN) printColored("> {}https://zxq.co/ripple/pep.py".format(bcolors.UNDERLINE), bcolors.GREEN)
printColored("> Press CTRL+C to exit\n", bcolors.GREEN) printColored("> Press CTRL+C to exit\n", bcolors.GREEN)
def printNoNl(string): def printNoNl(string):

View File

@ -1,6 +1,9 @@
import copy import copy
import json import json
import threading import threading
import time
from common.log import logUtils as log from common.log import logUtils as log
from constants import dataTypes from constants import dataTypes
from constants import matchModModes from constants import matchModModes
@ -62,6 +65,7 @@ class match:
self.isLocked = False # if True, users can't change slots/teams. Used in tourney matches self.isLocked = False # if True, users can't change slots/teams. Used in tourney matches
self.isStarting = False self.isStarting = False
self._lock = threading.Lock() self._lock = threading.Lock()
self.createTime = int(time.time())
# Create all slots and reset them # Create all slots and reset them
self.slots = [] self.slots = []

View File

@ -1,3 +1,6 @@
import threading
import time
from objects import match from objects import match
from objects import glob from objects import glob
from constants import serverPackets from constants import serverPackets
@ -63,3 +66,31 @@ class matchList:
glob.streams.broadcast("lobby", serverPackets.disposeMatch(matchID)) glob.streams.broadcast("lobby", serverPackets.disposeMatch(matchID))
del self.matches[matchID] del self.matches[matchID]
log.info("MPROOM{}: Room disposed manually".format(_match.matchID)) log.info("MPROOM{}: Room disposed manually".format(_match.matchID))
def cleanupLoop(self):
"""
Start match cleanup loop.
Empty matches that have been created more than 60 seconds ago will get deleted.
Useful when people create useless lobbies with `!mp make`.
The check is done every 30 seconds.
This method starts an infinite loop, call it only once!
:return:
"""
log.debug("Checking empty matches")
t = int(time.time())
emptyMatches = []
# Collect all empty matches
for key, m in self.matches.items():
if [x for x in m.slots if x.user is not None]:
continue
if t - m.createTime >= 120:
log.debug("Match #{} marked for cleanup".format(m.matchID))
emptyMatches.append(m.matchID)
# Dispose all empty matches
for matchID in emptyMatches:
self.disposeMatch(matchID)
# Schedule a new check (endless loop)
threading.Timer(30, self.cleanupLoop).start()

View File

@ -171,19 +171,16 @@ class tokenList:
for _, value in self.tokens.items(): for _, value in self.tokens.items():
value.enqueue(packet) value.enqueue(packet)
def usersTimeoutCheckLoop(self, timeoutTime = 100, checkTime = 100): def usersTimeoutCheckLoop(self):
""" """
Start timed out users disconnect loop. Start timed out users disconnect loop.
This function will be called every `checkTime` seconds and so on, forever. This function will be called every `checkTime` seconds and so on, forever.
CALL THIS FUNCTION ONLY ONCE! CALL THIS FUNCTION ONLY ONCE!
:param timeoutTime: seconds of inactivity required to disconnect someone. Default: 100
:param checkTime: seconds between loops. Default: 100
:return: :return:
""" """
log.debug("Checking timed out clients") log.debug("Checking timed out clients")
timedOutTokens = [] # timed out users timedOutTokens = [] # timed out users
timeoutLimit = int(time.time())-timeoutTime timeoutLimit = int(time.time()) - 100
for key, value in self.tokens.items(): for key, value in self.tokens.items():
# Check timeout (fokabot is ignored) # Check timeout (fokabot is ignored)
if value.pingTime < timeoutLimit and value.userID != 999 and value.irc == False and value.tournament == False: if value.pingTime < timeoutLimit and value.userID != 999 and value.irc == False and value.tournament == False:
@ -200,7 +197,7 @@ class tokenList:
del timedOutTokens del timedOutTokens
# Schedule a new check (endless loop) # Schedule a new check (endless loop)
threading.Timer(checkTime, self.usersTimeoutCheckLoop, [timeoutTime, checkTime]).start() threading.Timer(100, self.usersTimeoutCheckLoop).start()
def spamProtectionResetLoop(self): def spamProtectionResetLoop(self):
""" """

5
pep.py
View File

@ -187,6 +187,11 @@ if __name__ == "__main__":
glob.tokens.spamProtectionResetLoop() glob.tokens.spamProtectionResetLoop()
consoleHelper.printDone() consoleHelper.printDone()
# Initialize multiplayer cleanup loop
consoleHelper.printNoNl("> Initializing multiplayer cleanup loop... ")
glob.matches.cleanupLoop()
consoleHelper.printDone()
# Localize warning # Localize warning
glob.localize = generalUtils.stringToBool(glob.conf.config["localize"]["enable"]) glob.localize = generalUtils.stringToBool(glob.conf.config["localize"]["enable"])
if not glob.localize: if not glob.localize:

View File

@ -1,10 +1,10 @@
requests requests==2.18.1
tornado tornado==4.4.2
mysqlclient mysqlclient==1.3.9
psutil psutil==5.2.2
raven raven==5.32.0
bcrypt>=3.1.1 bcrypt==3.1.1
dill dill==0.2.7.1
redis redis==2.10.5
cython cython==0.27.3
datadog datadog==0.14.0

View File

@ -1 +1 @@
1.13.1 1.13.3