diff --git a/common b/common index cd9f453..0c8c4b9 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit cd9f453e2199c112bb3b8fb2e34d3019b1dae3b1 +Subproject commit 0c8c4b9e9883c399708c887498e58217d8702a9b diff --git a/constants/clientPackets.py b/constants/clientPackets.py index 5a693ab..41e4daf 100644 --- a/constants/clientPackets.py +++ b/constants/clientPackets.py @@ -1,4 +1,3 @@ -""" Contains functions used to read specific client packets from byte stream """ from constants import dataTypes from helpers import packetHelper from constants import slotStatuses @@ -100,7 +99,7 @@ def matchSettings(stream): start += 2 for i in range(0,16): s = data[0]["slot{}Status".format(str(i))] - if s != slotStatuses.free and s != slotStatuses.locked: + if s != slotStatuses.FREE and s != slotStatuses.LOCKED: start += 4 # Other settings diff --git a/constants/exceptions.py b/constants/exceptions.py index d46170d..95b3d38 100644 --- a/constants/exceptions.py +++ b/constants/exceptions.py @@ -1,5 +1,3 @@ -"""Bancho exceptions""" -# TODO: Prints in exceptions class loginFailedException(Exception): pass diff --git a/constants/fokabotCommands.py b/constants/fokabotCommands.py index 75d8df5..2eec0df 100644 --- a/constants/fokabotCommands.py +++ b/constants/fokabotCommands.py @@ -19,12 +19,12 @@ from objects import glob Commands callbacks Must have fro, chan and messages as arguments -fro -- name of who triggered the command -chan -- channel where the message was sent -message -- list containing arguments passed from the message - [0] = first argument - [1] = second argument - . . . +:param fro: username of who triggered the command +:param chan: channel"(or username, if PM) where the message was sent +:param message: list containing arguments passed from the message + [0] = first argument + [1] = second argument + . . . return the message or **False** if there's no response by the bot TODO: Change False to None, because False doesn't make any sense @@ -35,6 +35,7 @@ def instantRestart(fro, chan, message): return False def faq(fro, chan, message): + # TODO: Unhardcode this if message[0] == "rules": return "Please make sure to check (Ripple's rules)[http://ripple.moe/?p=23]." elif message[0] == "swearing": @@ -160,11 +161,11 @@ def silence(fro, chan, message): if unit == 's': silenceTime = int(amount) elif unit == 'm': - silenceTime = int(amount)*60 + silenceTime = int(amount) * 60 elif unit == 'h': - silenceTime = int(amount)*3600 + silenceTime = int(amount) * 3600 elif unit == 'd': - silenceTime = int(amount)*86400 + silenceTime = int(amount) * 86400 else: return "Invalid time unit (s/m/h/d)." @@ -707,11 +708,6 @@ callback: function to call when the command is triggered. Optional. response: text to return when the command is triggered. Optional. syntax: command syntax. Arguments must be separated by spaces (eg: ) privileges: privileges needed to execute the command. Optional. - -NOTES: -- You CAN'T use both rank and minRank at the same time. -- If both rank and minrank are **not** present, everyone will be able to run that command. -- You MUST set trigger and callback/response, or the command won't work. """ commands = [ { diff --git a/constants/matchModModes.py b/constants/matchModModes.py index 0b8ea87..746fe58 100644 --- a/constants/matchModModes.py +++ b/constants/matchModModes.py @@ -1,2 +1,2 @@ -normal = 0 -freeMod = 1 +NORMAL = 0 +FREE_MOD = 1 diff --git a/constants/matchScoringTypes.py b/constants/matchScoringTypes.py index 888f851..5d98114 100644 --- a/constants/matchScoringTypes.py +++ b/constants/matchScoringTypes.py @@ -1,3 +1,3 @@ -score = 0 -accuracy = 1 -combo = 2 +SCORE = 0 +ACCURACY = 1 +COMBO = 2 diff --git a/constants/matchTeamTypes.py b/constants/matchTeamTypes.py index 07d43bd..bd5babb 100644 --- a/constants/matchTeamTypes.py +++ b/constants/matchTeamTypes.py @@ -1,4 +1,4 @@ -headToHead = 0 -tagCoop = 1 -teamVs = 2 -tagTeamVs = 3 +HEAD_TO_HEAD = 0 +TAG_COOP = 1 +TEAM_VS = 2 +TAG_TEAM_VS = 3 diff --git a/constants/matchTeams.py b/constants/matchTeams.py index ef47898..d9cabe3 100644 --- a/constants/matchTeams.py +++ b/constants/matchTeams.py @@ -1,3 +1,3 @@ -noTeam = 0 -blue = 1 -red = 2 +NO_TEAM = 0 +BLUE = 1 +RED = 2 diff --git a/constants/serverPackets.py b/constants/serverPackets.py index 16ae3dc..4ba3802 100644 --- a/constants/serverPackets.py +++ b/constants/serverPackets.py @@ -159,7 +159,7 @@ def channelInfo(chan): return packetHelper.buildPacket(packetIDs.server_channelInfo, [ [chan, dataTypes.STRING], [channel.description, dataTypes.STRING], - [channel.getConnectedUsersCount(), dataTypes.UINT16] + [len(channel.connectedUsers), dataTypes.UINT16] ]) def channelInfoEnd(): diff --git a/constants/slotStatuses.py b/constants/slotStatuses.py index be36c78..711743c 100644 --- a/constants/slotStatuses.py +++ b/constants/slotStatuses.py @@ -1,8 +1,8 @@ -free = 1 -locked = 2 -notReady = 4 -ready = 8 -noMap = 16 -playing = 32 -occupied = 124 -playingQuit = 128 +FREE = 1 +LOCKED = 2 +NOT_READY = 4 +READY = 8 +NO_MAP = 16 +PLAYING = 32 +OCCUPIED = 124 +PLAYING_QUIT = 128 diff --git a/constants/userRanks.py b/constants/userRanks.py index cc372fe..bd65044 100644 --- a/constants/userRanks.py +++ b/constants/userRanks.py @@ -1,4 +1,3 @@ -"""Bancho user ranks""" NORMAL = 0 PLAYER = 1 BAT = 2 diff --git a/events/changeActionEvent.py b/events/changeActionEvent.py index 15b5f4d..c4421d1 100644 --- a/events/changeActionEvent.py +++ b/events/changeActionEvent.py @@ -17,9 +17,8 @@ def handle(userToken, packetData): return # Send restricted message if needed - if not userToken.restricted: - if userUtils.isRestricted(userID): - userToken.setRestricted() + if userToken.restricted: + userToken.checkRestricted(True) # Change action packet packetData = clientPackets.userActionChange(packetData) diff --git a/events/changeMatchModsEvent.py b/events/changeMatchModsEvent.py index 01f9367..3b9f107 100644 --- a/events/changeMatchModsEvent.py +++ b/events/changeMatchModsEvent.py @@ -18,7 +18,7 @@ def handle(userToken, packetData): match = glob.matches.matches[matchID] # Set slot or match mods according to modType - if match.matchModMode == matchModModes.freeMod: + if match.matchModMode == matchModModes.FREE_MOD: # Freemod # Host can set global DT/HT if userID == match.hostUserID: diff --git a/events/changeMatchSettingsEvent.py b/events/changeMatchSettingsEvent.py index 62b3d13..ddbd518 100644 --- a/events/changeMatchSettingsEvent.py +++ b/events/changeMatchSettingsEvent.py @@ -81,11 +81,11 @@ def handle(userToken, packetData): # Reset ready if needed if oldMods != match.mods or oldBeatmapMD5 != match.beatmapMD5: for i in range(0,16): - if match.slots[i].status == slotStatuses.ready: - match.slots[i].status = slotStatuses.notReady + if match.slots[i].status == slotStatuses.READY: + match.slots[i].status = slotStatuses.NOT_READY # Reset mods if needed - if match.matchModMode == matchModModes.normal: + if match.matchModMode == matchModModes.NORMAL: # Reset slot mods if not freeMods for i in range(0,16): match.slots[i].mods = 0 @@ -94,21 +94,21 @@ def handle(userToken, packetData): match.mods = 0 # Set/reset teams - if match.matchTeamType == matchTeamTypes.teamVs or match.matchTeamType == matchTeamTypes.tagTeamVs: + if match.matchTeamType == matchTeamTypes.TEAM_VS or match.matchTeamType == matchTeamTypes.TAG_TEAM_VS: # Set teams c=0 for i in range(0,16): - if match.slots[i].team == matchTeams.noTeam: - match.slots[i].team = matchTeams.red if c % 2 == 0 else matchTeams.blue + if match.slots[i].team == matchTeams.NO_TEAM: + match.slots[i].team = matchTeams.RED if c % 2 == 0 else matchTeams.BLUE c+=1 else: # Reset teams for i in range(0,16): - match.slots[i].team = matchTeams.noTeam + match.slots[i].team = matchTeams.NO_TEAM # Force no freemods if tag coop - if match.matchTeamType == matchTeamTypes.tagCoop or match.matchTeamType == matchTeamTypes.tagTeamVs: - match.matchModMode = matchModModes.normal + if match.matchTeamType == matchTeamTypes.TAG_COOP or match.matchTeamType == matchTeamTypes.TAG_TEAM_VS: + match.matchModMode = matchModModes.NORMAL # Send updated settings match.sendUpdates() diff --git a/events/loginEvent.py b/events/loginEvent.py index 730bd41..fca4555 100644 --- a/events/loginEvent.py +++ b/events/loginEvent.py @@ -192,19 +192,20 @@ def handle(tornadoRequest): # Get location and country from ip.zxq.co or database if glob.localize: # Get location and country from IP - location = locationHelper.getLocation(requestIP) + latitude, longitude = locationHelper.getLocation(requestIP) countryLetters = locationHelper.getCountry(requestIP) country = countryHelper.getCountryID(countryLetters) else: # Set location to 0,0 and get country from db log.warning("Location skipped") - location = [0,0] + latitude = 0 + longitude = 0 countryLetters = "XX" country = countryHelper.getCountryID(userUtils.getCountry(userID)) # Set location and country - responseToken.setLocation(location) - responseToken.setCountry(country) + responseToken.setLocation(latitude, longitude) + responseToken.country = country # Set country in db if user has no country (first bancho login) if userUtils.getCountry(userID) == "XX": diff --git a/events/setAwayMessageEvent.py b/events/setAwayMessageEvent.py index a1edcc2..f69a6e6 100644 --- a/events/setAwayMessageEvent.py +++ b/events/setAwayMessageEvent.py @@ -11,7 +11,7 @@ def handle(userToken, packetData): packetData = clientPackets.setAwayMessage(packetData) # Set token away message - userToken.setAwayMessage(packetData["awayMessage"]) + userToken.awayMessage = packetData["awayMessage"] # Send private message from fokabot if packetData["awayMessage"] == "": diff --git a/handlers/apiFokabotMessageHandler.py b/handlers/apiFokabotMessageHandler.py index b1128cc..244e94d 100644 --- a/handlers/apiFokabotMessageHandler.py +++ b/handlers/apiFokabotMessageHandler.py @@ -34,7 +34,5 @@ class handler(requestsManager.asyncRequestHandler): data["status"] = statusCode # Send response - #self.clear() self.write(json.dumps(data)) - self.set_status(statusCode) - #self.finish(json.dumps(data)) + self.set_status(statusCode) \ No newline at end of file diff --git a/handlers/apiIsOnlineHandler.py b/handlers/apiIsOnlineHandler.py index d14403a..8e42964 100644 --- a/handlers/apiIsOnlineHandler.py +++ b/handlers/apiIsOnlineHandler.py @@ -44,7 +44,5 @@ class handler(requestsManager.asyncRequestHandler): data["status"] = statusCode # Send response - #self.clear() self.write(json.dumps(data)) self.set_status(statusCode) - #self.finish(json.dumps(data)) diff --git a/handlers/apiOnlineUsersHandler.py b/handlers/apiOnlineUsersHandler.py index 6e6cb76..a35a6ed 100644 --- a/handlers/apiOnlineUsersHandler.py +++ b/handlers/apiOnlineUsersHandler.py @@ -20,7 +20,5 @@ class handler(requestsManager.asyncRequestHandler): data["status"] = statusCode # Send response - #self.clear() self.write(json.dumps(data)) self.set_status(statusCode) - #self.finish(json.dumps(data)) diff --git a/handlers/apiServerStatusHandler.py b/handlers/apiServerStatusHandler.py index 0ad753f..1f8a021 100644 --- a/handlers/apiServerStatusHandler.py +++ b/handlers/apiServerStatusHandler.py @@ -20,7 +20,5 @@ class handler(requestsManager.asyncRequestHandler): data["status"] = statusCode # Send response - #self.clear() self.write(json.dumps(data)) self.set_status(statusCode) - #self.finish(json.dumps(data)) diff --git a/handlers/mainHandler.py b/handlers/mainHandler.py index cad245b..557fa7a 100644 --- a/handlers/mainHandler.py +++ b/handlers/mainHandler.py @@ -115,8 +115,6 @@ class handler(SentryMixin, requestsManager.asyncRequestHandler): return wrapper eventHandler = { - # TODO: Rename packets and events - # TODO: Host check for multi packetIDs.client_changeAction: handleEvent(changeActionEvent), packetIDs.client_logout: handleEvent(logoutEvent), packetIDs.client_friendAdd: handleEvent(friendAddEvent), diff --git a/helpers/chatHelper.py b/helpers/chatHelper.py index 21d3c69..5ae7320 100644 --- a/helpers/chatHelper.py +++ b/helpers/chatHelper.py @@ -12,14 +12,11 @@ def joinChannel(userID = 0, channel = "", token = None, toIRC = True): """ Join a channel - userID -- user ID of the user that joins the channel. Optional. - token can be used instead. - token -- user token object of user that joins the channel. Optional. - userID can be used instead. - channel -- name of channe - toIRC -- if True, send this channel join event to IRC. Must be true if joining from bancho. - Optional. Defaukt: True - return -- returns 0 if joined or other IRC code in case of error. Needed only on IRC-side + :param userID: user ID of the user that joins the channel. Optional. token can be used instead. + :param token: user token object of user that joins the channel. Optional. userID can be used instead. + :param channel: channel name + :param toIRC: if True, send this channel join event to IRC. Must be true if joining from bancho. Default: True + :return: 0 if joined or other IRC code in case of error. Needed only on IRC-side """ try: # Get token if not defined @@ -77,15 +74,12 @@ def partChannel(userID = 0, channel = "", token = None, toIRC = True, kick = Fal """ Part a channel - userID -- user ID of the user that parts the channel. Optional. - token can be used instead. - token -- user token object of user that parts the channel. Optional. - userID can be used instead. - channel -- name of channel - toIRC -- if True, send this channel join event to IRC. Must be true if joining from bancho. - Optional. Defaukt: True - kick -- if True, channel tab will be closed on client. Used when leaving lobby. Optional. Default: False - return -- returns 0 if joined or other IRC code in case of error. Needed only on IRC-side + :param userID: user ID of the user that parts the channel. Optional. token can be used instead. + :param token: user token object of user that parts the channel. Optional. userID can be used instead. + :param channel: channel name + :param toIRC: if True, send this channel join event to IRC. Must be true if joining from bancho. Optional. Default: True + :param kick: if True, channel tab will be closed on client. Used when leaving lobby. Optional. Default: False + :return: 0 if joined or other IRC code in case of error. Needed only on IRC-side """ try: # Get token if not defined @@ -151,15 +145,12 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True): """ Send a message to osu!bancho and IRC server - fro -- sender username. Optional. - You can use token instead of this if you wish. - to -- receiver channel (if starts with #) or username - message -- text of the message - token -- sender token object. - You can use this instead of fro if you are sending messages from bancho. - Optional. - toIRC -- if True, send the message to IRC. If False, send it to Bancho only. - Optional. Default: True + :param fro: sender username. Optional. token can be used instead + :param to: receiver channel (if starts with #) or username + :param message: text of the message + :param token: sender token object. Optional. fro can be used instead + :param toIRC: if True, send the message to IRC. If False, send it to Bancho only. Default: True + :return: 0 if joined or other IRC code in case of error. Needed only on IRC-side """ try: tokenString = "" @@ -231,7 +222,7 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True): raise exceptions.channelNoPermissionsException # Everything seems fine, build recipients list and send packet - recipients = glob.channels.channels[to].getConnectedUsers()[:] + recipients = glob.channels.channels[to].connectedUsers[:] for key, value in glob.tokens.tokens.items(): # Skip our client and irc clients if key == tokenString or value.irc == True: @@ -312,6 +303,12 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True): """ IRC-Bancho Connect/Disconnect/Join/Part interfaces""" def fixUsernameForBancho(username): + """ + Convert username from IRC format (without spaces) to Bancho format (with spaces) + + :param username: username to convert + :return: converted username + """ # If there are no spaces or underscores in the name # return it if " " not in username and "_" not in username: @@ -326,9 +323,22 @@ def fixUsernameForBancho(username): return username.replace("_", " ") def fixUsernameForIRC(username): + """ + Convert an username from Bancho format to IRC format (underscores instead of spaces) + + :param username: username to convert + :return: converted username + """ return username.replace(" ", "_") def IRCConnect(username): + """ + Handle IRC login bancho-side. + Add token and broadcast login packet. + + :param username: username + :return: + """ userID = userUtils.getID(username) if not userID: log.warning("{} doesn't exist".format(username)) @@ -339,6 +349,13 @@ def IRCConnect(username): log.info("{} logged in from IRC".format(username)) def IRCDisconnect(username): + """ + Handle IRC logout bancho-side. + Remove token and broadcast logout packet. + + :param username: username + :return: + """ token = glob.tokens.getTokenFromUsername(username) if token is None: log.warning("{} doesn't exist".format(username)) @@ -347,6 +364,13 @@ def IRCDisconnect(username): log.info("{} disconnected from IRC".format(username)) def IRCJoinChannel(username, channel): + """ + Handle IRC channel join bancho-side. + + :param username: username + :param channel: channel name + :return: IRC return code + """ userID = userUtils.getID(username) if not userID: log.warning("{} doesn't exist".format(username)) @@ -357,6 +381,13 @@ def IRCJoinChannel(username, channel): return joinChannel(userID, channel) def IRCPartChannel(username, channel): + """ + Handle IRC channel part bancho-side. + + :param username: username + :param channel: channel name + :return: IRC return code + """ userID = userUtils.getID(username) if not userID: log.warning("{} doesn't exist".format(username)) @@ -364,9 +395,16 @@ def IRCPartChannel(username, channel): return partChannel(userID, channel) def IRCAway(username, message): + """ + Handle IRC away command bancho-side. + + :param username: + :param message: away message + :return: IRC return code + """ userID = userUtils.getID(username) if not userID: log.warning("{} doesn't exist".format(username)) return - glob.tokens.getTokenFromUserID(userID).setAwayMessage(message) + glob.tokens.getTokenFromUserID(userID).awayMessage = message return 305 if message == "" else 306 \ No newline at end of file diff --git a/helpers/configHelper.py b/helpers/configHelper.py index 378caf2..ee2919b 100644 --- a/helpers/configHelper.py +++ b/helpers/configHelper.py @@ -5,9 +5,9 @@ class config: # Check if config.ini exists and load/generate it def __init__(self, file): """ - Initialize a config object + Initialize a config file object - file -- filename + :param file: file name """ self.config = configparser.ConfigParser() self.default = True @@ -25,9 +25,9 @@ class config: # Check if config.ini has all needed the keys def checkConfig(self): """ - Check if this config has the required keys + Check is the config file has all required keys - return -- True if valid, False if not + :return: True if valid, False if not valid """ try: # Try to get all the required keys @@ -79,7 +79,9 @@ class config: def generateDefaultConfig(self): """ - Open and set default keys for that config file + Write a default config file to disk + + :return: """ # Open config.ini in write mode f = open(self.fileName, "w") diff --git a/helpers/consoleHelper.py b/helpers/consoleHelper.py index 268fe99..9297192 100644 --- a/helpers/consoleHelper.py +++ b/helpers/consoleHelper.py @@ -1,11 +1,12 @@ from common.constants import bcolors from objects import glob -def printServerStartHeader(asciiArt): +def printServerStartHeader(asciiArt=True): """ - Print server start header with optional ascii art + Print server start message - asciiArt -- if True, will print ascii art too + :param asciiArt: print BanchoBoat ascii art. Default: True + :return: """ if asciiArt: print("{} _ __".format(bcolors.GREEN)) @@ -32,35 +33,43 @@ def printServerStartHeader(asciiArt): def printNoNl(string): """ - Print string without new line at the end + Print a string without \n at the end - string -- string to print + :param string: string to print + :return: """ print(string, end="") def printColored(string, color): """ - Print colored string + Print a colored string - string -- string to print - color -- see bcolors.py + :param string: string to print + :param color: ANSI color code + :return: """ print("{}{}{}".format(color, string, bcolors.ENDC)) def printError(): """ - Print error text FOR LOADING + Print a red "Error" + + :return: """ printColored("Error", bcolors.RED) def printDone(): """ - Print error text FOR LOADING + Print a green "Done" + + :return: """ printColored("Done", bcolors.GREEN) def printWarning(): """ - Print error text FOR LOADING + Print a yellow "Warning" + + :return: """ printColored("Warning", bcolors.YELLOW) diff --git a/helpers/countryHelper.py b/helpers/countryHelper.py index 6932312..60257fb 100644 --- a/helpers/countryHelper.py +++ b/helpers/countryHelper.py @@ -1,5 +1,4 @@ -"""Contains all country codes with their osu numeric code""" - +# TODO: Update countries list countryCodes = { "LV": 132, "AD": 3, @@ -255,12 +254,11 @@ countryCodes = { def getCountryID(code): """ - Get country ID for osu client + Get osu country ID from country letters - code -- country name abbreviation (eg: US) - return -- country code int + :param code: country letters (eg: US) + :return: country osu code """ - if code in countryCodes: return countryCodes[code] else: @@ -270,10 +268,9 @@ def getCountryLetters(code): """ Get country letters from osu country ID - code -- country code int - return -- country name (2 letters) (XX if code not found) + :param code: osu country ID + :return: country letters (XX if not found) """ - for key, value in countryCodes.items(): if value == code: return key diff --git a/helpers/locationHelper.py b/helpers/locationHelper.py index eba28a4..25bc172 100644 --- a/helpers/locationHelper.py +++ b/helpers/locationHelper.py @@ -7,10 +7,10 @@ from objects import glob def getCountry(ip): """ - Get country from IP address + Get country from IP address using geoip api - ip -- IP Address - return -- Country code (2 letters) + :param ip: IP address + :return: country code. XX if invalid. """ try: # Try to get country from Pikolo Aul's Go-Sanic ip API @@ -22,15 +22,15 @@ def getCountry(ip): def getLocation(ip): """ - Get latitude and longitude from IP address + Get latitude and longitude from IP address using geoip api - ip -- IP address - return -- [latitude, longitude] + :param ip: IP address + :return: (latitude, longitude) """ try: # Try to get position from Pikolo Aul's Go-Sanic ip API result = json.loads(urllib.request.urlopen("{}/{}".format(glob.conf.config["localize"]["ipapiurl"], ip), timeout=3).read().decode())["loc"].split(",") - return [float(result[0]), float(result[1])] + return (float(result[0]), float(result[1])) except: log.error("Error in get position") - return [0,0] + return (0, 0) diff --git a/helpers/packetHelper.py b/helpers/packetHelper.py index 4f807b8..f7d1381 100644 --- a/helpers/packetHelper.py +++ b/helpers/packetHelper.py @@ -3,10 +3,10 @@ from constants import dataTypes def uleb128Encode(num): """ - Encode int -> uleb128 + Encode an int to uleb128 - num -- int to encode - return -- bytearray with encoded number + :param num: int to encode + :return: bytearray with encoded number """ arr = bytearray() length = 0 @@ -25,10 +25,10 @@ def uleb128Encode(num): def uleb128Decode(num): """ - Decode uleb128 -> int + Decode a uleb128 to int - num -- encoded uleb128 - return -- list. [total, length] + :param num: encoded uleb128 int + :return: (total, length) """ shift = 0 arr = [0,0] #total, length @@ -45,14 +45,12 @@ def uleb128Decode(num): def unpackData(data, dataType): """ - Unpacks data according to dataType + Unpacks a single section of a packet. - data -- bytes array to unpack - dataType -- data type. See dataTypes.py - - return -- unpacked bytes + :param data: bytes to unpack + :param dataType: data type + :return: unpacked bytes """ - # Get right pack Type if dataType == dataTypes.UINT16: unpackType = " Disposing server... ") @@ -27,7 +28,7 @@ def runningUnderUnix(): """ Get if the server is running under UNIX or NT - return --- True if running under UNIX, otherwise False + :return: True if running under UNIX, otherwise False """ return True if os.name == "posix" else False @@ -35,9 +36,11 @@ def scheduleShutdown(sendRestartTime, restart, message = "", delay=20): """ Schedule a server shutdown/restart - sendRestartTime -- time (seconds) to wait before sending server restart packets to every client - restart -- if True, server will restart. if False, server will shudown - message -- if set, send that message to every client to warn about the shutdown/restart + :param sendRestartTime: time (seconds) to wait before sending server restart packets to every client + :param restart: if True, server will restart. if False, server will shudown + :param message: if set, send that message to every client to warn about the shutdown/restart + :param delay: additional restart delay in seconds. Default: 20 + :return: """ # Console output log.info("Pep.py will {} in {} seconds!".format("restart" if restart else "shutdown", sendRestartTime+delay)) @@ -61,13 +64,21 @@ def scheduleShutdown(sendRestartTime, restart, message = "", delay=20): threading.Timer(sendRestartTime+delay, action).start() def restartServer(): - """Restart pep.py script""" + """ + Restart pep.py + + :return: + """ log.info("Restarting pep.py...") dispose() os.execv(sys.executable, [sys.executable] + sys.argv) def shutdownServer(): - """Shutdown pep.py""" + """ + Shutdown pep.py + + :return: + """ log.info("Shutting down pep.py...") dispose() sig = signal.SIGKILL if runningUnderUnix() else signal.CTRL_C_EVENT @@ -77,7 +88,7 @@ def getSystemInfo(): """ Get a dictionary with some system/server info - return -- ["unix", "connectedUsers", "webServer", "cpuUsage", "totalMemory", "usedMemory", "loadAverage"] + :return: ["unix", "connectedUsers", "webServer", "cpuUsage", "totalMemory", "usedMemory", "loadAverage"] """ data = {"unix": runningUnderUnix(), "connectedUsers": len(glob.tokens.tokens), "matches": len(glob.matches.matches)} diff --git a/irc/ircserver.py b/irc/ircserver.py index cd680fc..d086605 100644 --- a/irc/ircserver.py +++ b/irc/ircserver.py @@ -22,18 +22,15 @@ from objects import glob class Client: - """ - IRC Client object - """ __linesep_regexp = re.compile(r"\r?\n") - def __init__(self, server, sock): """ Initialize a Client object - server -- server object - sock -- socket connection object + :param server: server object + :param sock: socket connection object + :return: """ self.__timestamp = time.time() self.__readbuffer = "" @@ -60,7 +57,8 @@ class Client: Add a message (basic string) to client buffer. This is the lowest possible level. - msg -- message to add + :param msg: message to add + :return: """ self.__writebuffer += msg + "\r\n" @@ -69,7 +67,7 @@ class Client: """ Return this client's write buffer size - return -- write buffer size + :return: write buffer size """ return len(self.__writebuffer) @@ -78,7 +76,8 @@ class Client: """ Add an IRC-like message to client buffer. - msg -- message (without IRC stuff) + :param msg: message (without IRC stuff) + :return: """ self.message(":{} {}".format(self.server.host, msg)) @@ -87,10 +86,11 @@ class Client: """ Add an IRC-like message to client buffer with code - code -- response code - message -- response message - nickname -- receiver nickname - channel -- optional + :param code: response code + :param message: response message + :param nickname: receiver nickname + :param channel: optional + :return: """ if nickname == "": nickname = self.IRCUsername @@ -103,7 +103,8 @@ class Client: """ Add a 403 reply (no such channel) to client buffer. - channel -- meh + :param channel: + :return: """ self.replyCode(403, "{} :No such channel".format(channel)) @@ -112,7 +113,8 @@ class Client: """ Add a 461 reply (not enough parameters) to client buffer - command -- command that had not enough parameters + :param command: name of the command that had not enough parameters + :return: """ self.replyCode(403, "{} :Not enough parameters".format(command)) @@ -121,8 +123,9 @@ class Client: """ Disconnects this client from the IRC server - quitmsg -- IRC quit message. Default: 'Client quit' - callLogout -- if True, call logoutEvent on bancho + :param quitmsg: IRC quit message. Default: 'Client quit' + :param callLogout: if True, call logoutEvent on bancho + :return: """ # Send error to client and close socket self.message("ERROR :{}".format(quitmsg)) @@ -138,7 +141,11 @@ class Client: def readSocket(self): - """Read data coming from this client socket""" + """ + Read data coming from this client socket + + :return: + """ try: # Try to read incoming data from socket data = self.socket.recv(2 ** 10) @@ -161,7 +168,11 @@ class Client: def parseBuffer(self): - """Parse self.__readbuffer, get command, arguments and call its handler""" + """ + Parse self.__readbuffer, get command, arguments and call its handler + + :return: + """ # Get lines from buffer lines = self.__linesep_regexp.split(self.__readbuffer) self.__readbuffer = lines[-1] @@ -198,7 +209,11 @@ class Client: def writeSocket(self): - """Write buffer to socket""" + """ + Write buffer to socket + + :return: + """ try: sent = self.socket.send(self.__writebuffer.encode()) log.debug("[IRC] [{}:{}] <- {}".format(self.ip, self.port, self.__writebuffer[:sent])) @@ -206,9 +221,13 @@ class Client: except socket.error as x: self.disconnect(str(x)) - def checkAlive(self): - """Check if this client is still connected""" + """ + Check if this client is still connected. + If the client is dead, disconnect it. + + :return: + """ now = time.time() if self.__timestamp + 180 < now: self.disconnect("ping timeout") @@ -224,11 +243,19 @@ class Client: def sendLusers(self): - """Send lusers response to this client""" + """ + Send lusers response to this client + + :return: + """ self.replyCode(251, "There are {} users and 0 services on 1 server".format(len(glob.tokens.tokens))) def sendMotd(self): - """Send MOTD to this client""" + """ + Send MOTD to this client + + :return: + """ self.replyCode(375, "- {} Message of the day - ".format(self.server.host)) if len(self.server.motd) == 0: self.replyCode(422, "MOTD File is missing") @@ -340,13 +367,13 @@ class Client: # TODO: Part all channels if arguments[0] == "0": - return '''for (channelname, channel) in self.channels.items(): self.message_channel(channel, "PART", channelname, True) self.channel_log(channel, "left", meta=True) server.remove_member_from_channel(self, channelname) self.channels = {} return''' + return # Get channels to join list channels = arguments[0].split(",") @@ -375,7 +402,7 @@ class Client: self.replyCode(332, description, channel=channel) # Build connected users list - users = glob.channels.channels[channel].getConnectedUsers()[:] + users = glob.channels.channels[channel].connectedUsers[:] usernames = [] for user in users: token = glob.tokens.getTokenFromUserID(user) @@ -488,11 +515,14 @@ class Client: pass def awayHandler(self, command, arguments): + """AWAY command handler""" response = chat.IRCAway(self.banchoUsername, " ".join(arguments)) self.replyCode(response, "You are no longer marked as being away" if response == 305 else "You have been marked as being away") def mainHandler(self, command, arguments): - """Handler for post-login commands""" + """ + Handler for post-login commands + """ handlers = { "AWAY": self.awayHandler, #"ISON": ison_handler, @@ -522,17 +552,18 @@ class Client: class Server: def __init__(self, port): - #self.host = socket.getfqdn("127.0.0.1")[:63] self.host = glob.conf.config["irc"]["hostname"] self.port = port - self.clients = {} # Socket --> Client instance. + self.clients = {} # Socket - - > Client instance. self.motd = ["Welcome to pep.py's embedded IRC server!", "This is a VERY simple IRC server and it's still in beta.", "Expect things to crash and not work as expected :("] def forceDisconnection(self, username, isBanchoUsername=True): """ Disconnect someone from IRC if connected - username -- victim + :param username: victim + :param isBanchoUsername: if True, username is a bancho username, else convert it to a bancho username + :return: """ for _, value in self.clients.items(): if (value.IRCUsername == username and not isBanchoUsername) or (value.banchoUsername == username and isBanchoUsername): @@ -543,8 +574,9 @@ class Server: """ Let every IRC client connected to a specific client know that 'username' joined the channel from bancho - username -- username of bancho user - channel -- joined channel name + :param username: username of bancho user + :param channel: joined channel name + :return: """ username = chat.fixUsernameForIRC(username) for _, value in self.clients.items(): @@ -555,8 +587,9 @@ class Server: """ Let every IRC client connected to a specific client know that 'username' parted the channel from bancho - username -- username of bancho user - channel -- joined channel name + :param username: username of bancho user + :param channel: joined channel name + :return: """ username = chat.fixUsernameForIRC(username) for _, value in self.clients.items(): @@ -567,9 +600,10 @@ class Server: """ Send a message to IRC when someone sends it from bancho - fro -- sender username - to -- receiver username - message -- text of the message + :param fro: sender username + :param to: receiver username + :param message: text of the message + :return: """ fro = chat.fixUsernameForIRC(fro) to = chat.fixUsernameForIRC(to) @@ -589,14 +623,19 @@ class Server: """ Remove a client from connected clients - client -- client object - quitmsg -- QUIT argument, useless atm + :param client: client object + :param quitmsg: QUIT argument, useless atm + :return: """ if client.socket in self.clients: del self.clients[client.socket] def start(self): - """Start IRC server main loop""" + """ + Start IRC server main loop + + :return: + """ # Sentry if glob.sentry: sentryClient = raven.Client(glob.conf.config["sentry"]["ircdns"]) @@ -653,5 +692,11 @@ class Server: sentryClient.captureException() def main(port=6667): + """ + Create and start an IRC server + + :param port: IRC port. Default: 6667 + :return: + """ glob.ircServer = Server(port) glob.ircServer.start() diff --git a/objects/channel.py b/objects/channel.py index 7c94915..3681ce2 100644 --- a/objects/channel.py +++ b/objects/channel.py @@ -1,20 +1,16 @@ from objects import glob class channel: - """ - A chat channel - """ - def __init__(self, name, description, publicRead, publicWrite, temp, hidden): """ Create a new chat channel object - name -- channel name - description -- channel description - publicRead -- bool, if true channel can be read by everyone, if false it can be read only by mods/admins - publicWrite -- bool, same as public read but relative to write permissions - temp -- if True, channel will be deleted when there's no one in the channel - hidden -- if True, channel won't be shown in channels list + :param name: channel name + :param description: channel description + :param publicRead: if True, this channel can be read by everyone. If False, it can be read only by mods/admins + :param publicWrite: same as public read, but regards writing permissions + :param temp: if True, this channel will be deleted when there's no one in this channel + :param hidden: if True, thic channel won't be shown in channels list """ self.name = name self.description = description @@ -36,7 +32,8 @@ class channel: """ Add a user to connected users - userID -- user ID that joined the channel + :param userID: + :return: """ if userID not in self.connectedUsers: self.connectedUsers.append(userID) @@ -45,7 +42,8 @@ class channel: """ Remove a user from connected users - userID -- user ID that left the channel + :param userID: + :return: """ if userID in self.connectedUsers: self.connectedUsers.remove(userID) @@ -53,20 +51,4 @@ class channel: # Remove temp channels if empty or there's only fokabot connected l = len(self.connectedUsers) if self.temp == True and ((l == 0) or (l == 1 and 999 in self.connectedUsers)): - glob.channels.removeChannel(self.name) - - def getConnectedUsers(self): - """ - Get connected user IDs list - - return -- connectedUsers list - """ - return self.connectedUsers - - def getConnectedUsersCount(self): - """ - Count connected users - - return -- connected users number - """ - return len(self.connectedUsers) + glob.channels.removeChannel(self.name) \ No newline at end of file diff --git a/objects/channelList.py b/objects/channelList.py index aefb66f..daf659b 100644 --- a/objects/channelList.py +++ b/objects/channelList.py @@ -4,18 +4,14 @@ from objects import glob class channelList: - """ - Channel list - - channels -- dictionary. key: channel name, value: channel object - """ - channels = {} + def __init__(self): + self.channels = {} def loadChannels(self): """ - Load chat channels from db and add them to channels dictionary + Load chat channels from db and add them to channels list + :return: """ - # Get channels from DB channels = glob.db.fetchAll("SELECT * FROM bancho_channels") @@ -28,14 +24,15 @@ class channelList: def addChannel(self, name, description, publicRead, publicWrite, temp = False, hidden = False): """ - Add a channel object to channels dictionary + Add a channel to channels list - name -- channel name - description -- channel description - publicRead -- bool, if true channel can be read by everyone, if false it can be read only by mods/admins - publicWrite -- bool, same as public read but relative to write permissions - temp -- if True, channel will be deleted when there's no one in the channel. Optional. Default = False. - hidden -- if True, channel will be hidden in channels list. Optional. Default = False. + :param name: channel name + :param description: channel description + :param publicRead: if True, this channel can be read by everyone. If False, it can be read only by mods/admins + :param publicWrite: same as public read, but regards writing permissions + :param temp: if True, this channel will be deleted when there's no one in this channel + :param hidden: if True, thic channel won't be shown in channels list + :return: """ self.channels[name] = channel.channel(name, description, publicRead, publicWrite, temp, hidden) log.info("Created channel {}".format(name)) @@ -45,8 +42,8 @@ class channelList: Add a temporary channel (like #spectator or #multiplayer), gets deleted when there's no one in the channel and it's hidden in channels list - name -- channel name - return -- True if channel was created, False if failed + :param name: channel name + :return: True if the channel was created, otherwise False """ if name in self.channels: return False @@ -57,7 +54,8 @@ class channelList: """ Removes a channel from channels list - name -- channel name + :param name: channel name + :return: """ if name not in self.channels: log.debug("{} is not in channels list".format(name)) diff --git a/objects/chatFilters.py b/objects/chatFilters.py index af7f477..8142630 100644 --- a/objects/chatFilters.py +++ b/objects/chatFilters.py @@ -1,9 +1,20 @@ class chatFilters: def __init__(self, fileName="filters.txt"): + """ + Initialize chat filters + + :param fileName: name of the file containing filters. Default: filters.txt + """ self.filters = {} self.loadFilters(fileName) def loadFilters(self, fileName="filters.txt"): + """ + Load filters from a file + + :param fileName: name of the file containing filters. Default: filters.txt + :return: + """ # Reset chat filters self.filters = {} @@ -19,6 +30,12 @@ class chatFilters: self.filters[lineSplit[0].lower()] = lineSplit[1].replace("\n", "") def filterMessage(self, message): + """ + Replace forbidden words with filtered ones + + :param message: normal message + :return: filtered message + """ # Split words by spaces messageTemp = message.split(" ") diff --git a/objects/fokabot.py b/objects/fokabot.py index c8497fb..8597a12 100644 --- a/objects/fokabot.py +++ b/objects/fokabot.py @@ -12,29 +12,35 @@ from objects import glob npRegex = re.compile("^https?:\\/\\/osu\\.ppy\\.sh\\/b\\/(\\d*)") def connect(): - """Add FokaBot to connected users and send userpanel/stats packet to everyone""" + """ + Connect FokaBot to Bancho + + :return: + """ token = glob.tokens.addToken(999) token.actionID = actions.IDLE glob.streams.broadcast("main", serverPackets.userPanel(999)) glob.streams.broadcast("main", serverPackets.userStats(999)) def disconnect(): - """Remove FokaBot from connected users""" + """ + Disconnect FokaBot from Bancho + + :return: + """ glob.tokens.deleteToken(glob.tokens.getTokenFromUserID(999)) def fokabotResponse(fro, chan, message): """ - Check if a message has triggered fokabot (and return its response) + Check if a message has triggered FokaBot - fro -- sender username (for permissions stuff with admin commands) - chan -- channel name - message -- message - - return -- fokabot's response string or False + :param fro: sender username + :param chan: channel name (or receiver username) + :param message: chat mesage + :return: FokaBot's response or False if no response """ for i in fokabotCommands.commands: # Loop though all commands - #if i["trigger"] in message: if generalUtils.strContains(message, i["trigger"]): # message has triggered a command diff --git a/objects/glob.py b/objects/glob.py index 3c49e71..0fc5dd6 100644 --- a/objects/glob.py +++ b/objects/glob.py @@ -13,7 +13,7 @@ try: with open("version") as f: VERSION = f.read() if VERSION == "": - raise + raise Exception except: VERSION = "¯\_(xd)_/¯" diff --git a/objects/match.py b/objects/match.py index d29abe6..bf3dada 100644 --- a/objects/match.py +++ b/objects/match.py @@ -13,7 +13,7 @@ from objects import glob class slot: def __init__(self): - self.status = slotStatuses.free + self.status = slotStatuses.FREE self.team = 0 self.userID = -1 self.user = None @@ -23,19 +23,18 @@ class slot: self.complete = False class match: - """Multiplayer match object""" def __init__(self, matchID, matchName, matchPassword, beatmapID, beatmapName, beatmapMD5, gameMode, hostUserID): """ Create a new match object - matchID -- match progressive identifier - matchName -- match name, string - matchPassword -- match md5 password. Leave empty for no password - beatmapID -- beatmap ID - beatmapName -- beatmap name, string - beatmapMD5 -- beatmap md5 hash, string - gameMode -- game mode ID. See gameModes.py - hostUserID -- user id of the host + :param matchID: match progressive identifier + :param matchName: match name, string + :param matchPassword: match md5 password. Leave empty for no password + :param beatmapID: beatmap ID + :param beatmapName: beatmap name, string + :param beatmapMD5: beatmap md5 hash, string + :param gameMode: game mode ID. See gameModes.py + :param hostUserID: user id of the host """ self.matchID = matchID self.streamName = "multi/{}".format(self.matchID) @@ -49,9 +48,9 @@ class match: self.beatmapMD5 = beatmapMD5 self.hostUserID = hostUserID self.gameMode = gameMode - self.matchScoringType = matchScoringTypes.score # default values - self.matchTeamType = matchTeamTypes.headToHead # default value - self.matchModMode = matchModModes.normal # default value + self.matchScoringType = matchScoringTypes.SCORE # default values + self.matchTeamType = matchTeamTypes.HEAD_TO_HEAD # default value + self.matchModMode = matchModModes.NORMAL # default value self.seed = 0 self.matchDataCache = bytes() @@ -70,6 +69,8 @@ class match: def getMatchData(self): """ Return binary match data structure for packetHelper + + :return: """ # General match info # TODO: Test without safe copy, the error might have been caused by outdated python bytecode cache @@ -109,7 +110,7 @@ class match: ]) # Slot mods if free mod is enabled - if safeMatch.matchModMode == matchModModes.freeMod: + if safeMatch.matchModMode == matchModModes.FREE_MOD: for i in range(0,16): struct.append([safeMatch.slots[i].mods, dataTypes.UINT32]) @@ -123,7 +124,8 @@ class match: """ Set room host to newHost and send him host packet - newHost -- new host userID + :param newHost: new host userID + :return: """ slotID = self.getUserSlotID(newHost) if slotID is None or self.slots[slotID].user not in glob.tokens.tokens: @@ -135,7 +137,21 @@ class match: log.info("MPROOM{}: {} is now the host".format(self.matchID, token.username)) def setSlot(self, slotID, status = None, team = None, user = "", mods = None, loaded = None, skip = None, complete = None): - #self.setSlot(i, slotStatuses.notReady, 0, user, 0) + """ + Set data for a specific slot. + All fields but slotID are optional. + Skipped fields won't be edited. + + :param slotID: slot ID + :param status: new status + :param team: new team + :param user: new user id + :param mods: new mods + :param loaded: new loaded status + :param skip: new skip value + :param complete: new completed value + :return: + """ if status is not None: self.slots[slotID].status = status @@ -161,8 +177,9 @@ class match: """ Set slotID mods. Same as calling setSlot and then sendUpdate - slotID -- slot number - mods -- new mods + :param slotID: slot number + :param mods: new mods + :return: """ # Set new slot data and send update self.setSlot(slotID, mods=mods) @@ -174,14 +191,15 @@ class match: Switch slotID ready/not ready status Same as calling setSlot and then sendUpdate - slotID -- slot number + :param slotID: slot number + :return: """ # Update ready status and setnd update oldStatus = self.slots[slotID].status - if oldStatus == slotStatuses.ready: - newStatus = slotStatuses.notReady + if oldStatus == slotStatuses.READY: + newStatus = slotStatuses.NOT_READY else: - newStatus = slotStatuses.ready + newStatus = slotStatuses.READY self.setSlot(slotID, newStatus) self.sendUpdates() log.info("MPROOM{}: Slot{} changed ready status to {}".format(self.matchID, slotID, self.slots[slotID].status)) @@ -191,13 +209,14 @@ class match: Lock a slot Same as calling setSlot and then sendUpdate - slotID -- slot number + :param slotID: slot number + :return: """ # Check if slot is already locked - if self.slots[slotID].status == slotStatuses.locked: - newStatus = slotStatuses.free + if self.slots[slotID].status == slotStatuses.LOCKED: + newStatus = slotStatuses.FREE else: - newStatus = slotStatuses.locked + newStatus = slotStatuses.LOCKED # Send updated settings to kicked user, so he returns to lobby if self.slots[slotID].user is not None and self.slots[slotID].user in glob.tokens.tokens: @@ -208,13 +227,14 @@ class match: # Send updates to everyone else self.sendUpdates() - log.info("MPROOM{}: Slot{} {}".format(self.matchID, slotID, "locked" if newStatus == slotStatuses.locked else "unlocked")) + log.info("MPROOM{}: Slot{} {}".format(self.matchID, slotID, "locked" if newStatus == slotStatuses.LOCKED else "unlocked")) def playerLoaded(self, userID): """ Set a player loaded status to True - userID -- ID of user + :param userID: ID of user + :return: """ slotID = self.getUserSlotID(userID) if slotID is None: @@ -228,7 +248,7 @@ class match: total = 0 loaded = 0 for i in range(0,16): - if self.slots[i].status == slotStatuses.playing: + if self.slots[i].status == slotStatuses.PLAYING: total+=1 if self.slots[i].loaded: loaded+=1 @@ -237,7 +257,11 @@ class match: self.allPlayersLoaded() def allPlayersLoaded(self): - """Send allPlayersLoaded packet to every playing usr in match""" + """ + Send allPlayersLoaded packet to every playing usr in match + + :return: + """ glob.streams.broadcast(self.playingStreamName, serverPackets.allPlayersLoaded()) log.info("MPROOM{}: All players loaded! Match starting...".format(self.matchID)) @@ -245,7 +269,8 @@ class match: """ Set a player skip status to True - userID -- ID of user + :param userID: ID of user + :return: """ slotID = self.getUserSlotID(userID) if slotID is None: @@ -263,7 +288,7 @@ class match: total = 0 skipped = 0 for i in range(0,16): - if self.slots[i].status == slotStatuses.playing: + if self.slots[i].status == slotStatuses.PLAYING: total+=1 if self.slots[i].skip: skipped+=1 @@ -272,7 +297,11 @@ class match: self.allPlayersSkipped() def allPlayersSkipped(self): - """Send allPlayersSkipped packet to every playing usr in match""" + """ + Send allPlayersSkipped packet to every playing usr in match + + :return: + """ glob.streams.broadcast(self.playingStreamName, serverPackets.allPlayersSkipped()) log.info("MPROOM{}: All players have skipped!".format(self.matchID)) @@ -280,7 +309,7 @@ class match: """ Set userID's slot completed to True - userID -- ID of user + :param userID: ID of user """ slotID = self.getUserSlotID(userID) if slotID is None: @@ -294,7 +323,7 @@ class match: total = 0 completed = 0 for i in range(0,16): - if self.slots[i].status == slotStatuses.playing: + if self.slots[i].status == slotStatuses.PLAYING: total+=1 if self.slots[i].complete: completed+=1 @@ -303,15 +332,18 @@ class match: self.allPlayersCompleted() def allPlayersCompleted(self): - """Cleanup match stuff and send match end packet to everyone""" + """ + Cleanup match stuff and send match end packet to everyone + :return: + """ # Reset inProgress self.inProgress = False # Reset slots for i in range(0,16): - if self.slots[i].user is not None and self.slots[i].status == slotStatuses.playing: - self.slots[i].status = slotStatuses.notReady + if self.slots[i].user is not None and self.slots[i].status == slotStatuses.PLAYING: + self.slots[i].status = slotStatuses.NOT_READY self.slots[i].loaded = False self.slots[i].skip = False self.slots[i].complete = False @@ -332,7 +364,7 @@ class match: """ Get slot ID occupied by userID - return -- slot id if found, None if user is not in room + :return: slot id if found, None if user is not in room """ for i in range(0,16): if self.slots[i].user is not None and self.slots[i].user in glob.tokens.tokens and glob.tokens.tokens[self.slots[i].user].userID == userID: @@ -343,21 +375,20 @@ class match: """ Add someone to users in match - userID -- user id of the user - return -- True if join success, False if fail (room is full) + :param userID: user id of the user + :return: True if join success, False if fail (room is full) """ - # Make sure we're not in this match for i in range(0,16): if self.slots[i].user == user.token: # Set bugged slot to free - self.setSlot(i, slotStatuses.free, 0, None, 0) + self.setSlot(i, slotStatuses.FREE, 0, None, 0) # Find first free slot for i in range(0,16): - if self.slots[i].status == slotStatuses.free: + if self.slots[i].status == slotStatuses.FREE: # Occupy slot - self.setSlot(i, slotStatuses.notReady, 0, user.token, 0) + self.setSlot(i, slotStatuses.NOT_READY, 0, user.token, 0) # Send updated match data self.sendUpdates() @@ -372,7 +403,8 @@ class match: """ Remove someone from users in match - userID -- user if of the user + :param userID: user if of the user + :return: """ # Make sure the user is in room slotID = self.getUserSlotID(user.userID) @@ -380,7 +412,7 @@ class match: return # Set that slot to free - self.setSlot(slotID, slotStatuses.free, 0, None, 0) + self.setSlot(slotID, slotStatuses.FREE, 0, None, 0) # Check if everyone left if self.countUsers() == 0: @@ -407,8 +439,9 @@ class match: """ Change userID slot to newSlotID - userID -- user that changed slot - newSlotID -- slot id of new slot + :param userID: user that changed slot + :param newSlotID: slot id of new slot + :return: """ # Make sure the user is in room oldSlotID = self.getUserSlotID(userID) @@ -416,7 +449,7 @@ class match: return # Make sure there is no one inside new slot - if self.slots[newSlotID].user is not None and self.slots[newSlotID].status != slotStatuses.free: + if self.slots[newSlotID].user is not None and self.slots[newSlotID].status != slotStatuses.FREE: return # Get old slot data @@ -424,7 +457,7 @@ class match: oldData = copy.deepcopy(self.slots[oldSlotID]) # Free old slot - self.setSlot(oldSlotID, slotStatuses.free, 0, None, 0, False, False, False) + self.setSlot(oldSlotID, slotStatuses.FREE, 0, None, 0, False, False, False) # Occupy new slot self.setSlot(newSlotID, oldData.status, oldData.team, oldData.user, oldData.mods) @@ -439,13 +472,10 @@ class match: """ Change match password to newPassword - newPassword -- new password string + :param newPassword: new password string + :return: """ self.matchPassword = newPassword - #if newPassword != "": - # self.matchPassword = generalUtils.stringMd5(newPassword) - #else: - # self.matchPassword = "" # Send password change to every user in match glob.streams.broadcast(self.streamName, serverPackets.changeMatchPassword(self.matchPassword)) @@ -460,7 +490,8 @@ class match: """ Set match global mods - mods -- mods bitwise int thing + :param mods: mods bitwise int thing + :return: """ # Set new mods and send update self.mods = mods @@ -471,8 +502,9 @@ class match: """ Set no beatmap status for userID - userID -- ID of user - has -- True if has beatmap, false if not + :param userID: ID of user + :param has: True if has beatmap, false if not + :return: """ # Make sure the user is in room slotID = self.getUserSlotID(userID) @@ -480,7 +512,7 @@ class match: return # Set slot - self.setSlot(slotID, slotStatuses.noMap if not has else slotStatuses.notReady) + self.setSlot(slotID, slotStatuses.NO_MAP if not has else slotStatuses.NOT_READY) # Send updates self.sendUpdates() @@ -489,7 +521,8 @@ class match: """ Transfer host to slotID - slotID -- ID of slot + :param slotID: ID of slot + :return: """ # Make sure there is someone in that slot if self.slots[slotID].user is None or self.slots[slotID].user not in glob.tokens.tokens: @@ -505,7 +538,8 @@ class match: """ Send userID's failed packet to everyone in match - userID -- ID of user + :param userID: ID of user + :return: """ # Make sure the user is in room slotID = self.getUserSlotID(userID) @@ -522,10 +556,10 @@ class match: """ Fro invites to in this match. - fro -- sender userID - to -- receiver userID + :param fro: sender userID + :param to: receiver userID + :return: """ - # Get tokens froToken = glob.tokens.getTokenFromUserID(fro) toToken = glob.tokens.getTokenFromUserID(to) @@ -544,7 +578,7 @@ class match: """ Return how many players are in that match - return -- number of users + :return: number of users """ c = 0 for i in range(0,16): @@ -556,7 +590,8 @@ class match: """ Change userID's team - userID -- id of user + :param userID: id of user + :return: """ # Make sure the user is in room slotID = self.getUserSlotID(userID) @@ -564,11 +599,16 @@ class match: return # Update slot and send update - newTeam = matchTeams.blue if self.slots[slotID].team == matchTeams.red else matchTeams.red + newTeam = matchTeams.BLUE if self.slots[slotID].team == matchTeams.RED else matchTeams.RED self.setSlot(slotID, None, newTeam) self.sendUpdates() def sendUpdates(self): + """ + Send match updates packet to everyone in lobby and room streams + + :return: + """ self.matchDataCache = serverPackets.updateMatch(self.matchID) if self.matchDataCache is not None: glob.streams.broadcast(self.streamName, self.matchDataCache) @@ -580,16 +620,17 @@ class match: """ Check if match teams are valid - return -- True if valid, False if invalid + :return: True if valid, False if invalid + :return: """ - if self.matchTeamType != matchTeamTypes.teamVs or self.matchTeamType != matchTeamTypes.tagTeamVs: + if self.matchTeamType != matchTeamTypes.TEAM_VS or self.matchTeamType != matchTeamTypes.TAG_TEAM_VS: # Teams are always valid if we have no teams return True # We have teams, check if they are valid firstTeam = -1 for i in range(0,16): - if self.slots[i].user is not None and (self.slots[i].status & slotStatuses.noMap) == 0: + if self.slots[i].user is not None and (self.slots[i].status & slotStatuses.NO_MAP) == 0: if firstTeam == -1: firstTeam = self.slots[i].team elif firstTeam != self.slots[i].team: @@ -600,6 +641,11 @@ class match: return False def start(self): + """ + Start the match + + :return: + """ # Make sure we have enough players if self.countUsers() < 2 or not self.checkTeams(): return @@ -613,8 +659,8 @@ class match: # Set playing to ready players and set load, skip and complete to False # Make clients join playing stream for i in range(0, 16): - if (self.slots[i].status & slotStatuses.ready) > 0 and self.slots[i].user in glob.tokens.tokens: - self.slots[i].status = slotStatuses.playing + if (self.slots[i].status & slotStatuses.READY) > 0 and self.slots[i].user in glob.tokens.tokens: + self.slots[i].status = slotStatuses.PLAYING self.slots[i].loaded = False self.slots[i].skip = False self.slots[i].complete = False diff --git a/objects/matchList.py b/objects/matchList.py index 36adad9..3b6c2d7 100644 --- a/objects/matchList.py +++ b/objects/matchList.py @@ -13,14 +13,14 @@ class matchList: """ Add a new match to matches list - matchName -- match name, string - matchPassword -- match md5 password. Leave empty for no password - beatmapID -- beatmap ID - beatmapName -- beatmap name, string - beatmapMD5 -- beatmap md5 hash, string - gameMode -- game mode ID. See gameModes.py - hostUserID -- user id of who created the match - return -- match ID + :param matchName: match name, string + :param matchPassword: match md5 password. Leave empty for no password + :param beatmapID: beatmap ID + :param beatmapName: beatmap name, string + :param beatmapMD5: beatmap md5 hash, string + :param gameMode: game mode ID. See gameModes.py + :param hostUserID: user id of who created the match + :return: match ID """ # Add a new match to matches list and create its stream matchID = self.lastID @@ -32,7 +32,8 @@ class matchList: """ Destroy match object with id = matchID - matchID -- ID of match to dispose + :param matchID: ID of match to dispose + :return: """ # Make sure the match exists if matchID not in self.matches: diff --git a/objects/osuToken.py b/objects/osuToken.py index 965453f..162b8eb 100644 --- a/objects/osuToken.py +++ b/objects/osuToken.py @@ -12,17 +12,17 @@ from objects import glob class token: - def __init__(self, userID, token_ = None, ip ="", irc = False, timeOffset = 0, tournament = False): """ Create a token object and set userID and token - userID -- user associated to this token - token -- if passed, set token to that value - if not passed, token will be generated - ip -- client ip. optional. - irc -- if True, set this token as IRC client. optional. - timeOffset -- the time offset from UTC for this user. optional. + :param userID: user associated to this token + :param token_: if passed, set token to that value + if not passed, token will be generated + :param ip: client ip. optional. + :param irc: if True, set this token as IRC client. Default: False. + :param timeOffset: the time offset from UTC for this user. Default: 0. + :param tournament: if True, flag this client as a tournement client. Default: True. """ # Set stuff self.userID = userID @@ -95,25 +95,23 @@ class token: """ Add bytes (packets) to queue - bytes -- (packet) bytes to enqueue + :param bytes: (packet) bytes to enqueue """ - if not self.irc: - if len(bytes_) < 10 * 10 ** 6: - self.queue += bytes_ - else: - log.warning("{}'s packets buffer is above 10M!! Lost some data!".format(self.username)) - + # TODO: reduce max queue size + if len(bytes_) < 10 * 10 ** 6: + self.queue += bytes_ + else: + log.warning("{}'s packets buffer is above 10M!! Lost some data!".format(self.username)) def resetQueue(self): """Resets the queue. Call when enqueued packets have been sent""" self.queue = bytes() - def joinChannel(self, channel): """ Add channel to joined channels list - channel -- channel name + :param channel: channel name """ if channel not in self.joinedChannels: self.joinedChannels.append(channel) @@ -122,24 +120,24 @@ class token: """ Remove channel from joined channels list - channel -- channel name + :param channel: channel name """ if channel in self.joinedChannels: self.joinedChannels.remove(channel) - def setLocation(self, location): + def setLocation(self, latitude, longitude): """ - Set location (latitude and longitude) + Set client location - location -- [latitude, longitude] + :param location: [latitude, longitude] """ - self.location = location + self.location = (latitude, longitude) def getLatitude(self): """ Get latitude - return -- latitude + :return: latitude """ return self.location[0] @@ -147,15 +145,16 @@ class token: """ Get longitude - return -- longitude + :return: longitude """ return self.location[1] def startSpectating(self, host): """ - Set the spectating user to userID + Set the spectating user to userID, join spectator stream and chat channel + and send required packets to host - user -- user object + :param host: host osuToken object """ # Stop spectating old client self.stopSpectating() @@ -195,6 +194,12 @@ class token: log.info("{} is spectating {}".format(self.username, host.username)) def stopSpectating(self): + """ + Stop spectating, leave spectator stream and channel + and send required packets to host + + :return: + """ # Remove our userID from host's spectators if self.spectating is None: return @@ -233,36 +238,20 @@ class token: self.spectating = None self.spectatingUserID = 0 - def setCountry(self, countryID): - """ - Set country to countryID - - countryID -- numeric country ID. See countryHelper.py - """ - self.country = countryID - - def getCountry(self): - """ - Get numeric country ID - - return -- numeric country ID. See countryHelper.py - """ - return self.country - def updatePingTime(self): - """Update latest ping time""" + """ + Update latest ping time to current time + + :return: + """ self.pingTime = int(time.time()) - def setAwayMessage(self, __awayMessage): - """Set a new away message""" - self.awayMessage = __awayMessage - - def joinMatch(self, matchID): """ Set match to matchID, join match stream and channel - matchID -- new match ID + :param matchID: new match ID + :return: """ # Make sure the match exists if matchID not in glob.matches.matches: @@ -322,7 +311,10 @@ class token: """ Kick this user from the server - message -- Notification message to send to this user. Optional. + :param message: Notification message to send to this user. + Default: "You have been kicked from the server. Please login again." + :param reason: Kick reason, used in logs. Default: "kick" + :return: """ # Send packet to target log.info("{} has been disconnected. ({})".format(self.username, reason)) @@ -337,9 +329,10 @@ class token: """ Silences this user (db, packet and token) - seconds -- silence length in seconds - reason -- silence reason - author -- userID of who has silenced the target. Optional. Default: 999 (fokabot) + :param seconds: silence length in seconds + :param reason: silence reason + :param author: userID of who has silenced the user. Default: 999 (FokaBot) + :return: """ # Silence in db and token self.silenceEndTime = int(time.time())+seconds @@ -355,7 +348,8 @@ class token: """ Silences the user if is spamming. - increaseSpamRate -- pass True if the user has sent a new message. Optional. Default: True + :param increaseSpamRate: set to True if the user has sent a new message. Default: True + :return: """ # Increase the spam rate if needed if increaseSpamRate: @@ -369,7 +363,7 @@ class token: """ Returns True if this user is silenced, otherwise False - return -- True/False + :return: True if this user is silenced, otherwise False """ return self.silenceEndTime-int(time.time()) > 0 @@ -378,12 +372,16 @@ class token: Returns the seconds left for this user's silence (0 if user is not silenced) - return -- silence seconds left + :return: silence seconds left (or 0) """ return max(0, self.silenceEndTime-int(time.time())) def updateCachedStats(self): - """Update all cached stats for this token""" + """ + Update all cached stats for this token + + :return: + """ stats = userUtils.getUserStats(self.userID, self.gameMode) log.debug(str(stats)) if stats is None: @@ -400,8 +398,9 @@ class token: """ Check if this token is restricted. If so, send fokabot message - force -- If True, get restricted value from db. - If false, get the cached one. Optional. Default: False + :param force: If True, get restricted value from db. + If False, get the cached one. Default: False + :return: """ if force: self.restricted = userUtils.isRestricted(self.userID) @@ -412,21 +411,40 @@ class token: """ Set this token as restricted, send FokaBot message to user and send offline packet to everyone + + :return: """ self.restricted = True chat.sendMessage("FokaBot",self.username, "Your account is currently in restricted mode. Please visit ripple's website for more information.") def joinStream(self, name): + """ + Join a packet stream, or create it if the stream doesn't exist. + + :param name: stream name + :return: + """ glob.streams.join(name, token=self.token) if name not in self.streams: self.streams.append(name) def leaveStream(self, name): + """ + Leave a packets stream + + :param name: stream name + :return: + """ glob.streams.leave(name, token=self.token) if name in self.streams: self.streams.remove(name) def leaveAllStreams(self): + """ + Leave all joined packet streams + + :return: + """ for i in self.streams: self.leaveStream(i) diff --git a/objects/tokenList.py b/objects/tokenList.py index 8b9569d..0f90668 100644 --- a/objects/tokenList.py +++ b/objects/tokenList.py @@ -8,27 +8,19 @@ from events import logoutEvent from objects import glob from objects import osuToken - class tokenList: - """ - List of connected osu tokens - - tokens -- dictionary. key: token string, value: token object - """ - def __init__(self): - """ - Initialize a tokens list - """ self.tokens = {} def addToken(self, userID, ip = "", irc = False, timeOffset=0, tournament=False): """ Add a token object to tokens list - userID -- user id associated to that token - irc -- if True, set this token as IRC client - return -- token object + :param userID: user id associated to that token + :param irc: if True, set this token as IRC client + :param timeOffset: the time offset from UTC for this user. Default: 0. + :param tournament: if True, flag this client as a tournement client. Default: True. + :return: token object """ newToken = osuToken.token(userID, ip=ip, irc=irc, timeOffset=timeOffset, tournament=tournament) self.tokens[newToken.token] = newToken @@ -38,7 +30,8 @@ class tokenList: """ Delete a token from token list if it exists - token -- token string + :param token: token string + :return: """ if token in self.tokens: # Delete session from DB @@ -52,8 +45,8 @@ class tokenList: """ Get user ID from a token - token -- token to find - return -- false if not found, userID if found + :param token: token to find + :return: False if not found, userID if found """ # Make sure the token exists if token not in self.tokens: @@ -66,8 +59,9 @@ class tokenList: """ Get token from a user ID - userID -- user ID to find - return -- False if not found, token object if found + :param userID: user ID to find + :param ignoreIRC: if True, consider bancho clients only and skip IRC clients + :return: False if not found, token object if found """ # Make sure the token exists for _, value in self.tokens.items(): @@ -85,8 +79,8 @@ class tokenList: :param username: normal username or safe username :param ignoreIRC: if True, consider bancho clients only and skip IRC clients - :param safe: if True, username is a safe username, - compare it with token's safe username rather than normal username + :param safe: if True, username is a safe username, + compare it with token's safe username rather than normal username :return: osuToken object or None """ # lowercase @@ -106,7 +100,8 @@ class tokenList: """ Delete old userID's tokens if found - userID -- tokens associated to this user will be deleted + :param userID: tokens associated to this user will be deleted + :return: """ # Delete older tokens for key, value in list(self.tokens.items()): @@ -118,9 +113,10 @@ class tokenList: """ Enqueue a packet to multiple users - packet -- packet bytes to enqueue - who -- userIDs array - but -- if True, enqueue to everyone but users in who array + :param packet: packet bytes to enqueue + :param who: userIDs array + :param but: if True, enqueue to everyone but users in `who` array + :return: """ for _, value in self.tokens.items(): shouldEnqueue = False @@ -136,19 +132,21 @@ class tokenList: """ Enqueue packet(s) to every connected user - packet -- packet bytes to enqueue + :param packet: packet bytes to enqueue + :return: """ for _, value in self.tokens.items(): value.enqueue(packet) def usersTimeoutCheckLoop(self, timeoutTime = 100, checkTime = 100): """ - Deletes all timed out users. - If called once, will recall after checkTime seconds and so on, forever + Start timed out users disconnect loop. + This function will be called every `checkTime` seconds and so on, forever. CALL THIS FUNCTION ONLY ONCE! - timeoutTime - seconds of inactivity required to disconnect someone (Default: 100) - checkTime - seconds between loops (Default: 100) + :param timeoutTime: seconds of inactivity required to disconnect someone. Default: 100 + :param checkTime: seconds between loops. Default: 100 + :return: """ log.debug("Checking timed out clients") timedOutTokens = [] # timed out users @@ -172,8 +170,11 @@ class tokenList: def spamProtectionResetLoop(self): """ - Reset spam rate every 10 seconds. + Start spam protection reset loop. + Called every 10 seconds. CALL THIS FUNCTION ONLY ONCE! + + :return: """ # Reset spamRate for every token for _, value in self.tokens.items(): @@ -186,20 +187,22 @@ class tokenList: """ Truncate bancho_sessions table. Call at bancho startup to delete old cached sessions + + :return: """ glob.db.execute("TRUNCATE TABLE bancho_sessions") + def tokenExists(self, username = "", userID = -1): """ - Check if a token exists (aka check if someone is connected) - - username -- Optional. - userID -- Optional. - return -- True if it exists, otherwise False - + Check if a token exists Use username or userid, not both at the same time. + + :param username: Optional. + :param userID: Optional. + :return: True if it exists, otherwise False """ if userID > -1: return True if self.getTokenFromUserID(userID) is not None else False else: - return True if self.getTokenFromUsername(username) is not None else False + return True if self.getTokenFromUsername(username) is not None else False \ No newline at end of file