def systemMaintenance(fro, chan, message): # Turn on/off bancho maintenance maintenance = True # Get on/off if len(message) >= 2: if message[1] == "off": maintenance = False # Set new maintenance value in bancho_settings table glob.banchoConf.setMaintenance(maintenance) if maintenance == True: # We have turned on maintenance mode # Users that will be disconnected who = [] # Disconnect everyone but mod/admins for _, value in glob.tokens.tokens.items(): if value.rank < 3: who.append(value.userID) glob.tokens.enqueueAll(serverPackets.notification("Our bancho server is in maintenance mode. Please try to login again later.")) glob.tokens.multipleEnqueue(serverPackets.loginError(), who) msg = "The server is now in maintenance mode!" else: # We have turned off maintenance mode # Send message if we have turned off maintenance mode msg = "The server is no longer in maintenance mode!" # Chat output return msg
def systemMaintenance(fro, chan, message): # Turn on/off bancho maintenance maintenance = True # Get on/off if len(message) >= 2: if message[1] == "off": maintenance = False # Set new maintenance value in bancho_settings table glob.banchoConf.setMaintenance(maintenance) if maintenance == True: # We have turned on maintenance mode # Users that will be disconnected who = [] # Disconnect everyone but mod/admins for _, value in glob.tokens.tokens.items(): if value.rank < 3: who.append(value.userID) glob.tokens.enqueueAll( serverPackets.notification( "Our bancho server is in maintenance mode. Please try to login again later." )) glob.tokens.multipleEnqueue(serverPackets.loginError(), who) msg = "The server is now in maintenance mode!" else: # We have turned off maintenance mode # Send message if we have turned off maintenance mode msg = "The server is no longer in maintenance mode!" # Chat output return msg
def handle(flaskRequest): # Data to return responseTokenString = "ayy" responseData = bytes() # Get IP from flask request requestIP = flaskRequest.headers.get('X-Real-IP') if requestIP == None: requestIP = flaskRequest.remote_addr # Console output print("> Accepting connection from {}...".format(requestIP)) # Split POST body so we can get username/password/hardware data # 2:-3 thing is because requestData has some escape stuff that we don't need loginData = str(flaskRequest.data)[2:-3].split("\\n") # Process login print("> Processing login request for {}...".format(loginData[0])) try: # If true, print error to console err = False # Try to get the ID from username userID = userHelper.getID(str(loginData[0])) if userID == False: # Invalid username raise exceptions.loginFailedException() if userHelper.checkLogin(userID, loginData[1]) == False: # Invalid password raise exceptions.loginFailedException() # Make sure we are not banned userAllowed = userHelper.getAllowed(userID) if userAllowed == 0: # Banned raise exceptions.loginBannedException() # No login errors! # Delete old tokens for that user and generate a new one glob.tokens.deleteOldTokens(userID) responseToken = glob.tokens.addToken(userID) responseTokenString = responseToken.token # Get silence end userSilenceEnd = max( 0, userHelper.getSilenceEnd(userID) - int(time.time())) # Get supporter/GMT userRank = userHelper.getRankPrivileges(userID) userGMT = False userSupporter = True if userRank >= 3: userGMT = True # Server restarting check if glob.restarting == True: raise exceptions.banchoRestartingException() # Maintenance check if glob.banchoConf.config["banchoMaintenance"] == True: if userGMT == False: # We are not mod/admin, delete token, send notification and logout glob.tokens.deleteToken(responseTokenString) raise exceptions.banchoMaintenanceException() else: # We are mod/admin, send warning notification and continue responseToken.enqueue( serverPackets.notification( "Bancho is in maintenance mode. Only mods/admins have full access to the server.\nType !system maintenance off in chat to turn off maintenance mode." )) # Send all needed login packets responseToken.enqueue(serverPackets.silenceEndTime(userSilenceEnd)) responseToken.enqueue(serverPackets.userID(userID)) responseToken.enqueue(serverPackets.protocolVersion()) responseToken.enqueue( serverPackets.userSupporterGMT(userSupporter, userGMT)) responseToken.enqueue(serverPackets.userPanel(userID)) responseToken.enqueue(serverPackets.userStats(userID)) # Channel info end (before starting!?! wtf bancho?) responseToken.enqueue(serverPackets.channelInfoEnd()) # Default opened channels # TODO: Configurable default channels channelJoinEvent.joinChannel(responseToken, "#osu") channelJoinEvent.joinChannel(responseToken, "#announce") if userRank >= 3: # Join admin chanenl if we are mod/admin # TODO: Separate channels for mods and admins channelJoinEvent.joinChannel(responseToken, "#admin") # Output channels info for key, value in glob.channels.channels.items(): if value.publicRead == True: responseToken.enqueue(serverPackets.channelInfo(key)) responseToken.enqueue(serverPackets.friendList(userID)) # Send main menu icon and login notification if needed if glob.banchoConf.config["menuIcon"] != "": responseToken.enqueue( serverPackets.mainMenuIcon(glob.banchoConf.config["menuIcon"])) if glob.banchoConf.config["loginNotification"] != "": responseToken.enqueue( serverPackets.notification( glob.banchoConf.config["loginNotification"])) # Get everyone else userpanel # TODO: Better online users handling for key, value in glob.tokens.tokens.items(): responseToken.enqueue(serverPackets.userPanel(value.userID)) responseToken.enqueue(serverPackets.userStats(value.userID)) # Send online users IDs array responseToken.enqueue(serverPackets.onlineUsers()) # Get location and country from ip.zxq.co or database if generalFunctions.stringToBool( glob.conf.config["server"]["localizeusers"]): # Get location and country from IP location = locationHelper.getLocation(requestIP) country = countryHelper.getCountryID( locationHelper.getCountry(requestIP)) else: # Set location to 0,0 and get country from db print("[!] Location skipped") location = [0, 0] country = countryHelper.getCountryID(userHelper.getCountry(userID)) # Set location and country responseToken.setLocation(location) responseToken.setCountry(country) # Send to everyone our userpanel and userStats (so they now we have logged in) glob.tokens.enqueueAll(serverPackets.userPanel(userID)) glob.tokens.enqueueAll(serverPackets.userStats(userID)) # Set reponse data to right value and reset our queue responseData = responseToken.queue responseToken.resetQueue() # Print logged in message consoleHelper.printColored( "> {} logged in ({})".format(loginData[0], responseToken.token), bcolors.GREEN) except exceptions.loginFailedException: # Login failed error packet # (we don't use enqueue because we don't have a token since login has failed) err = True responseData += serverPackets.loginFailed() except exceptions.loginBannedException: # Login banned error packet err = True responseData += serverPackets.loginBanned() except exceptions.banchoMaintenanceException: # Bancho is in maintenance mode responseData += serverPackets.notification( "Our bancho server is in maintenance mode. Please try to login again later." ) responseData += serverPackets.loginError() except exceptions.banchoRestartingException: # Bancho is restarting responseData += serverPackets.notification( "Bancho is restarting. Try again in a few minutes.") responseData += serverPackets.loginError() finally: # Print login failed message to console if needed if err == True: consoleHelper.printColored( "> {}'s login failed".format(loginData[0]), bcolors.YELLOW) return (responseTokenString, responseData)
def fokabotResponse(fro, chan, message): """ Check if a message has triggered fokabot (and return its response) fro -- sender username (for permissions stuff with admin commands) chan -- channel name message -- message return -- fokabot's response string or False """ if "!roll" in message: maxPoints = 100 message = message.split(" ") # Get max number if needed if (len(message) >= 2): if (message[1].isdigit() == True and int(message[1]) > 0): maxPoints = int(message[1]) points = random.randrange(0, maxPoints) return fro + " rolls " + str(points) + " points!" elif "!faq rules" in message: return "Please make sure to check (Ripple's rules)[http://ripple.moe/?p=23]." elif "!faq swearing" in message: return "Please don't abuse swearing" elif "!faq spam" in message: return "Please don't spam" elif "!faq offend" in message: return "Please offend other players" elif "!help" in message: return "Click (here)[https://ripple.moe/index.php?p=16&id=4] for full FokaBot's command list" elif "!report" in message: return "Report command is not here yet :(" # Admin commands elif "!moderated" in message: try: # Admin check if (userHelper.getUserRank(userHelper.getUserID(fro)) <= 1): raise exceptions.noAdminException # Make sure we are in a channel and not PM if (chan.startswith("#") == False): raise exceptions.moderatedPMException # Split message and default value message = message.lower().split(" ") enable = True # Get on/off if (len(message) >= 2): if (message[1] == "off"): enable = False # Turn on/off moderated mode glob.channels.channels[chan].moderated = enable return "This channel is {} in moderated mode!".format( "now" if enable else "no longer") except exceptions.noAdminException: consoleHelper.printColored( "[!] " + fro + " has tried to put " + chan + " in moderated mode, but he is not an admin.", bcolors.RED) return False except exceptions.moderatedPMException: consoleHelper.printColored( "[!] " + fro + " has tried to put a PM chat in moderated mode.", bcolors.RED) return "Are you trying to put a private chat in moderated mode? Are you serious?!? You're fired." elif "!system" in message: # System commands try: # Admin check if (userHelper.getUserRank(userHelper.getUserID(fro)) <= 1): raise exceptions.noAdminException # Split message message = message.lower().split(" ") # Get parameters if (len(message) >= 2): if (message[1] == "restart"): msg = "We are performing some maintenance. Bancho will restart in 5 seconds. Thank you for your patience." systemHelper.scheduleShutdown(5, True, msg) return msg elif (message[1] == "status"): # Print some server info data = systemHelper.getSystemInfo() # Final message msg = "=== PEP.PY STATS ===\n" msg += "Running pep.py server\n" msg += "Webserver: " + data["webServer"] + "\n" msg += "\n" msg += "=== BANCHO STATS ===\n" msg += "Connected users: " + str( data["connectedUsers"]) + "\n" msg += "\n" msg += "=== SYSTEM STATS ===\n" msg += "CPU: " + str(data["cpuUsage"]) + "%\n" msg += "RAM: " + str(data["usedMemory"]) + "GB/" + str( data["totalMemory"]) + "GB\n" if (data["unix"] == True): msg += "Load average: " + str( data["loadAverage"][0]) + "/" + str( data["loadAverage"][1]) + "/" + str( data["loadAverage"][2]) + "\n" return msg elif (message[1] == "reload"): #Reload settings from bancho_settings glob.banchoConf.loadSettings() return "Bancho settings reloaded!" elif (message[1] == "maintenance"): # Turn on/off bancho maintenance maintenance = True # Get on/off if (len(message) >= 2): if (message[2] == "off"): maintenance = False # Set new maintenance value in bancho_settings table glob.banchoConf.setMaintenance(maintenance) if (maintenance == True): # We have turned on maintenance mode # Users that will be disconnected who = [] # Disconnect everyone but mod/admins for key, value in glob.tokens.tokens.items(): if (value.rank <= 2): who.append(value.userID) glob.tokens.enqueueAll( serverPackets.notification( "Our bancho server is in maintenance mode. Please try to login again later." )) glob.tokens.multipleEnqueue(serverPackets.loginError(), who) msg = "The server is now in maintenance mode!" else: # We have turned off maintenance mode # Send message if we have turned off maintenance mode msg = "The server is no longer in maintenance mode!" # Chat output return msg else: raise exceptions.commandSyntaxException except exceptions.noAdminException: consoleHelper.printColored( "[!] " + fro + " has tried to run a system command, but he is not an admin.", bcolors.RED) return False except exceptions.commandSyntaxException: consoleHelper.printColored("[!] Fokabot command syntax error", bcolors.RED) return False elif "!scareall" in message: try: # Admin check if (userHelper.getUserRank(userHelper.getUserID(fro)) <= 1): raise exceptions.noAdminException # Get parameters message = message.lower().split(" ") if (len(message) < 2): raise exceptions.commandSyntaxException scaryMessage = ' '.join(message[1:]) # Send packet to everyone consoleHelper.printColored( "> {} is turning osu! into an horror game ({})".format( fro, scaryMessage), bcolors.PINK) glob.tokens.enqueueAll(serverPackets.jumpscare(scaryMessage)) except exceptions.noAdminException: pass except exceptions.commandSyntaxException: return "Wrong syntax. !scareall <message>" finally: # No respobnse return False elif "!scare" in message: try: # Admin check if (userHelper.getUserRank(userHelper.getUserID(fro)) <= 1): raise exceptions.noAdminException # Get parameters message = message.lower().split(" ") if (len(message) < 3): raise exceptions.commandSyntaxException target = message[1] scaryMessage = ' '.join(message[2:]) # Get target token and make sure is connected targetToken = glob.tokens.getTokenFromUsername(target) if (targetToken == None): raise exceptions.tokenNotFoundException # Send packet to target consoleHelper.printColored( "> Rip {}'s heart ({}). ~ <3, {}".format( target, scaryMessage, fro), bcolors.PINK) targetToken.enqueue(serverPackets.jumpscare(scaryMessage)) # No response return False except exceptions.noAdminException: return False except exceptions.tokenNotFoundException: return "{} is not online".format(message[1]) except exceptions.commandSyntaxException: return "Wrong syntax. !scare <target> <message>" elif "!kick" in message: try: # Admin check # TODO: God this sucks if (userHelper.getUserRank(userHelper.getUserID(fro)) <= 1): raise exceptions.noAdminException # Get parameters message = message.lower().split(" ") if (len(message) < 2): raise exceptions.commandSyntaxException target = message[1] # Get target token and make sure is connected targetToken = glob.tokens.getTokenFromUsername(target) if (targetToken == None): raise exceptions.tokenNotFoundException # Send packet to target consoleHelper.printColored( "> {} has been disconnected. (kick)".format(target), bcolors.YELLOW) targetToken.enqueue( serverPackets.notification( "You have been kicked from the server. Please login again." )) targetToken.enqueue(serverPackets.loginFailed()) # Bot response return "{} has been kicked from the server.".format(message[1]) except exceptions.noAdminException: return False except exceptions.tokenNotFoundException: return "{} is not online.".format(message[1]) except exceptions.commandSyntaxException: return "Wrong syntax. !kick <target>" elif "!silence" in message: try: # Admin check if (userHelper.getUserRank(userHelper.getUserID(fro)) <= 1): raise exceptions.noAdminException # Get parameters message = message.lower().split(" ") if (len(message) < 4): raise exceptions.commandSyntaxException target = message[1] amount = message[2] unit = message[3] reason = ' '.join(message[4:]) # Get target user ID targetUserID = userHelper.getUserID(target) # Make sure the user exists if (targetUserID == False): raise exceptions.userNotFoundException # Calculate silence seconds if (unit == 's'): silenceTime = int(amount) elif (unit == 'm'): silenceTime = int(amount) * 60 elif (unit == 'h'): silenceTime = int(amount) * 3600 elif (unit == 'd'): silenceTime = int(amount) * 86400 else: raise exceptions.commandSyntaxException # Max silence time is 7 days if (silenceTime > 604800): raise exceptions.commandSyntaxException # Calculate silence end time endTime = int(time.time()) + silenceTime # Update silence end in db userHelper.silenceUser(targetUserID, endTime, reason) # Check if target is connected targetToken = glob.tokens.getTokenFromUsername(target) if (targetToken == None): tokenFound = False else: tokenFound = True # Send silence packets if user is online if (tokenFound == True): targetToken.enqueue(serverPackets.silenceEndTime(silenceTime)) consoleHelper.printColored( "{} has been silenced for {} seconds the following reason: {}". format(target, silenceTime, reason), bcolors.PINK) # Bot response return "{} has been silenced for the following reason: {}".format( target, reason) except exceptions.userNotFoundException: return "{}: user not found".format(message[1]) except exceptions.noAdminException: return False except exceptions.commandSyntaxException: return "Wrong syntax. !silence <target> <amount> <unit (s/m/h/d)> <reason>. Max silence time is 7 days." elif "!removesilence" in message or "!resetsilence" in message: try: # Admin check if (userHelper.getUserRank(userHelper.getUserID(fro)) <= 1): raise exceptions.noAdminException # Get parameters message = message.lower().split(" ") if (len(message) < 2): raise exceptions.commandSyntaxException target = message[1] # Make sure the user exists targetUserID = userHelper.getUserID(target) if (targetUserID == False): raise exceptions.userNotFoundException # Reset user silence time and reason in db userHelper.silenceUser(targetUserID, 0, "") # Send new silence end packet to user if he's online targetToken = glob.tokens.getTokenFromUsername(target) if (targetToken != None): targetToken.enqueue(serverPackets.silenceEndTime(0)) return "{}'s silence reset".format(target) except exceptions.commandSyntaxException: return "Wrong syntax. !removesilence <target>" except exceptions.noAdminException: return False except exceptions.userNotFoundException: return "{}: user not found".format(message[1]) elif "!fokabot reconnect" in message: try: # Admin check if (userHelper.getUserRank(userHelper.getUserID(fro)) <= 1): raise exceptions.noAdminException # Check if fokabot is already connected if (glob.tokens.getTokenFromUserID(999) != None): raise exceptions.alreadyConnectedException # Fokabot is not connected, connect it connect() return False except exceptions.noAdminException: return False except exceptions.alreadyConnectedException: return "Fokabot is already connected to Bancho" else: return False
def handle(flaskRequest): # Data to return responseTokenString = "ayy" responseData = bytes() # Get IP from flask request requestIP = flaskRequest.headers.get('X-Real-IP') if (requestIP == None): requestIP = flaskRequest.remote_addr # Console output print("> Accepting connection from {}...".format(requestIP)) # Split POST body so we can get username/password/hardware data # 2:-3 thing is because requestData has some escape stuff that we don't need loginData = str(flaskRequest.data)[2:-3].split("\\n") # Process login print("> Processing login request for {}...".format(loginData[0])) try: # If true, print error to console err = False # Try to get the ID from username userID = userHelper.getUserID(str(loginData[0])) if (userID == False): # Invalid username raise exceptions.loginFailedException() if (userHelper.checkLogin(userID, loginData[1]) == False): # Invalid password raise exceptions.loginFailedException() # Make sure we are not banned userAllowed = userHelper.getUserAllowed(userID) if (userAllowed == 0): # Banned raise exceptions.loginBannedException() # No login errors! # Delete old tokens for that user and generate a new one glob.tokens.deleteOldTokens(userID) responseToken = glob.tokens.addToken(userID) responseTokenString = responseToken.token # Get silence end userSilenceEnd = max(0, userHelper.getUserSilenceEnd(userID)-int(time.time())) # Get supporter/GMT userRank = userHelper.getUserRank(userID) userGMT = False userSupporter = True if (userRank >= 3): userGMT = True # Maintenance check if (glob.banchoConf.config["banchoMaintenance"] == True): if (userGMT == False): # We are not mod/admin, delete token, send notification and logout glob.tokens.deleteToken(responseTokenString) raise exceptions.banchoMaintenanceException() else: # We are mod/admin, send warning notification and continue responseToken.enqueue(serverPackets.notification("Bancho is in maintenance mode. Only mods/admins have full access to the server.\nType !system maintenance off in chat to turn off maintenance mode.")) # Send all needed login packets responseToken.enqueue(serverPackets.silenceEndTime(userSilenceEnd)) responseToken.enqueue(serverPackets.userID(userID)) responseToken.enqueue(serverPackets.protocolVersion()) responseToken.enqueue(serverPackets.userSupporterGMT(userSupporter, userGMT)) responseToken.enqueue(serverPackets.userPanel(userID)) responseToken.enqueue(serverPackets.userStats(userID)) # Channel info end (before starting!?! wtf bancho?) responseToken.enqueue(serverPackets.channelInfoEnd()) # Default opened channels # TODO: Configurable default channels channelJoinEvent.joinChannel(responseToken, "#osu") channelJoinEvent.joinChannel(responseToken, "#announce") if (userRank >= 3): # Join admin chanenl if we are mod/admin # TODO: Separate channels for mods and admins channelJoinEvent.joinChannel(responseToken, "#admin") # Output channels info for key, value in glob.channels.channels.items(): responseToken.enqueue(serverPackets.channelInfo(key)) responseToken.enqueue(serverPackets.friendList(userID)) # Send main menu icon and login notification if needed if (glob.banchoConf.config["menuIcon"] != ""): responseToken.enqueue(serverPackets.mainMenuIcon(glob.banchoConf.config["menuIcon"])) if (glob.banchoConf.config["loginNotification"] != ""): responseToken.enqueue(serverPackets.notification(glob.banchoConf.config["loginNotification"])) # Get everyone else userpanel # TODO: Better online users handling for key, value in glob.tokens.tokens.items(): responseToken.enqueue(serverPackets.userPanel(value.userID)) responseToken.enqueue(serverPackets.userStats(value.userID)) # Send online users IDs array responseToken.enqueue(serverPackets.onlineUsers()) # Get location and country from ip.zxq.co or database if (generalFunctions.stringToBool(glob.conf.config["server"]["localizeusers"])): # Get location and country from IP location = locationHelper.getLocation(requestIP) country = countryHelper.getCountryID(locationHelper.getCountry(requestIP)) else: # Set location to 0,0 and get country from db print("[!] Location skipped") location = [0,0] country = countryHelper.getCountryID(userHelper.getCountry(userID)) # Set location and country responseToken.setLocation(location) responseToken.setCountry(country) # Send to everyone our userpanel and userStats (so they now we have logged in) glob.tokens.enqueueAll(serverPackets.userPanel(userID)) glob.tokens.enqueueAll(serverPackets.userStats(userID)) # Set reponse data to right value and reset our queue responseData = responseToken.queue responseToken.resetQueue() # Print logged in message consoleHelper.printColored("> {} logged in ({})".format(loginData[0], responseToken.token), bcolors.GREEN) except exceptions.loginFailedException: # Login failed error packet # (we don't use enqueue because we don't have a token since login has failed) err = True responseData += serverPackets.loginFailed() except exceptions.loginBannedException: # Login banned error packet err = True responseData += serverPackets.loginBanned() except exceptions.banchoMaintenanceException: # Bancho is in maintenance mode responseData += serverPackets.notification("Our bancho server is in maintenance mode. Please try to login again later.") responseData += serverPackets.loginError() finally: # Print login failed message to console if needed if (err == True): consoleHelper.printColored("> {}'s login failed".format(loginData[0]), bcolors.YELLOW) return (responseTokenString, responseData)
def banchoServer(): if (flask.request.method == 'POST'): # Track time if needed if (serverOutputRequestTime == True): # Start time st = datetime.datetime.now() # Client's token string and request data requestTokenString = flask.request.headers.get('osu-token') requestData = flask.request.data # Server's token string and request data responseTokenString = "ayy" responseData = bytes() if (requestTokenString == None): # No token, first request. Handle login. responseTokenString, responseData = loginEvent.handle(flask.request) else: try: # This is not the first packet, send response based on client's request # Packet start position, used to read stacked packets pos = 0 # Make sure the token exists if (requestTokenString not in glob.tokens.tokens): raise exceptions.tokenNotFoundException() # Token exists, get its object userToken = glob.tokens.tokens[requestTokenString] # Keep reading packets until everything has been read while pos < len(requestData): # Get packet from stack starting from new packet leftData = requestData[pos:] # Get packet ID, data length and data packetID = packetHelper.readPacketID(leftData) dataLength = packetHelper.readPacketLength(leftData) packetData = requestData[pos:(pos+dataLength+7)] # Console output if needed if (serverOutputPackets == True and packetID != 4): consoleHelper.printColored("Incoming packet ({})({}):".format(requestTokenString, userToken.username), bcolors.GREEN) consoleHelper.printColored("Packet code: {}\nPacket length: {}\nSingle packet data: {}\n".format(str(packetID), str(dataLength), str(packetData)), bcolors.YELLOW) # Event handler def handleEvent(ev): def wrapper(): ev.handle(userToken, packetData) return wrapper eventHandler = { # TODO: Rename packets and events # TODO: Host check for multi packetIDs.client_sendPublicMessage: handleEvent(sendPublicMessageEvent), packetIDs.client_sendPrivateMessage: handleEvent(sendPrivateMessageEvent), packetIDs.client_setAwayMessage: handleEvent(setAwayMessageEvent), packetIDs.client_channelJoin: handleEvent(channelJoinEvent), packetIDs.client_channelPart: handleEvent(channelPartEvent), packetIDs.client_changeAction: handleEvent(changeActionEvent), packetIDs.client_startSpectating: handleEvent(startSpectatingEvent), packetIDs.client_stopSpectating: handleEvent(stopSpectatingEvent), packetIDs.client_cantSpectate: handleEvent(cantSpectateEvent), packetIDs.client_spectateFrames: handleEvent(spectateFramesEvent), packetIDs.client_friendAdd: handleEvent(friendAddEvent), packetIDs.client_friendRemove: handleEvent(friendRemoveEvent), packetIDs.client_logout: handleEvent(logoutEvent), packetIDs.client_joinLobby: handleEvent(joinLobbyEvent), packetIDs.client_partLobby: handleEvent(partLobbyEvent), packetIDs.client_createMatch: handleEvent(createMatchEvent), packetIDs.client_joinMatch: handleEvent(joinMatchEvent), packetIDs.client_partMatch: handleEvent(partMatchEvent), packetIDs.client_matchChangeSlot: handleEvent(changeSlotEvent), packetIDs.client_matchChangeSettings: handleEvent(changeMatchSettingsEvent), packetIDs.client_matchChangePassword: handleEvent(changeMatchPasswordEvent), packetIDs.client_matchChangeMods: handleEvent(changeMatchModsEvent), packetIDs.client_matchReady: handleEvent(matchReadyEvent), packetIDs.client_matchNotReady: handleEvent(matchReadyEvent), packetIDs.client_matchLock: handleEvent(matchLockEvent), packetIDs.client_matchStart: handleEvent(matchStartEvent), packetIDs.client_matchLoadComplete: handleEvent(matchPlayerLoadEvent), packetIDs.client_matchSkipRequest: handleEvent(matchSkipEvent), packetIDs.client_matchScoreUpdate: handleEvent(matchFramesEvent), packetIDs.client_matchComplete: handleEvent(matchCompleteEvent), packetIDs.client_matchNoBeatmap: handleEvent(matchNoBeatmapEvent), packetIDs.client_matchHasBeatmap: handleEvent(matchHasBeatmapEvent), packetIDs.client_matchTransferHost: handleEvent(matchTransferHostEvent), packetIDs.client_matchFailed: handleEvent(matchFailedEvent), packetIDs.client_invite: handleEvent(matchInviteEvent), packetIDs.client_matchChangeTeam: handleEvent(matchChangeTeamEvent) } if packetID != 4: if packetID in eventHandler: eventHandler[packetID]() else: consoleHelper.printColored("[!] Unknown packet id from {} ({})".format(requestTokenString, packetID), bcolors.RED) # Update pos so we can read the next stacked packet # +7 because we add packet ID bytes, unused byte and data length bytes pos += dataLength+7 # Token queue built, send it responseTokenString = userToken.token responseData = userToken.queue userToken.resetQueue() # Update ping time for timeout userToken.updatePingTime() except exceptions.tokenNotFoundException: # Token not found. Disconnect that user responseData = serverPackets.loginError() responseData += serverPackets.notification("Whoops! Something went wrong, please login again.") consoleHelper.printColored("[!] Received packet from unknown token ({}).".format(requestTokenString), bcolors.RED) consoleHelper.printColored("> {} have been disconnected (invalid token)".format(requestTokenString), bcolors.YELLOW) if (serverOutputRequestTime == True): # End time et = datetime.datetime.now() # Total time: tt = float((et.microsecond-st.microsecond)/1000) consoleHelper.printColored("Request time: {}ms".format(tt), bcolors.PINK) # Send server's response to client # We don't use token object because we might not have a token (failed login) return responseHelper.generateResponse(responseTokenString, responseData) else: # Not a POST request, send html page return responseHelper.HTMLResponse()
def banchoServer(): if (flask.request.method == 'POST'): # Client's token requestToken = flask.request.headers.get('osu-token') # Client's request data # We remove the first two and last three characters because they are # some escape stuff that we don't need requestData = flask.request.data # Client's IP requestIP = flask.request.headers.get('X-Real-IP') if (requestIP == None): requestIP = flask.request.remote_addr # Server's response data responseData = bytes() # Server's response token string responseTokenString = "ayy"; if (requestToken == None): # We don't have a token, this is the first packet aka login print("> Accepting connection from "+requestIP+"...") # Split POST body so we can get username/password/hardware data loginData = str(requestData)[2:-3].split("\\n") # Process login print("> Processing login request for "+loginData[0]+"...") try: # If true, print error to console err = False # Try to get the ID from username userID = userHelper.getUserID(str(loginData[0])) if (userID == False): # Invalid username raise exceptions.loginFailedException() if (userHelper.checkLogin(userID, loginData[1]) == False): # Invalid password raise exceptions.loginFailedException() # Make sure we are not banned userAllowed = userHelper.getUserAllowed(userID) if (userAllowed == 0): # Banned raise exceptions.loginBannedException() # No login errors! # Delete old tokens for that user and generate a new one glob.tokens.deleteOldTokens(userID) responseToken = glob.tokens.addToken(userID) responseTokenString = responseToken.token # Print logged in message consoleHelper.printColored("> "+loginData[0]+" logged in ("+responseToken.token+")", bcolors.GREEN) # Get silence end userSilenceEnd = max(0, userHelper.getUserSilenceEnd(userID)-int(time.time())) # Get supporter/GMT userRank = userHelper.getUserRank(userID) userGMT = False userSupporter = True if (userRank >= 3): userGMT = True # Maintenance check if (glob.banchoConf.config["banchoMaintenance"] == True): if (userGMT == False): # We are not mod/admin, delete token, send notification and logout glob.tokens.deleteToken(responseTokenString) raise exceptions.banchoMaintenanceException() else: # We are mod/admin, send warning notification and continue responseToken.enqueue(serverPackets.notification("Bancho is in maintenance mode. Only mods/admins have full access to the server.\nType !system maintenance off in chat to turn off maintenance mode.")) # Send all needed login packets responseToken.enqueue(serverPackets.silenceEndTime(userSilenceEnd)) responseToken.enqueue(serverPackets.userID(userID)) responseToken.enqueue(serverPackets.protocolVersion()) responseToken.enqueue(serverPackets.userSupporterGMT(userSupporter, userGMT)) responseToken.enqueue(serverPackets.userPanel(userID)) responseToken.enqueue(serverPackets.userStats(userID)) # Channel info end (before starting!?! wtf bancho?) responseToken.enqueue(serverPackets.channelInfoEnd()) # TODO: Configurable default channels # Default opened channels glob.channels.channels["#osu"].userJoin(userID) responseToken.joinChannel("#osu") glob.channels.channels["#announce"].userJoin(userID) responseToken.joinChannel("#announce") responseToken.enqueue(serverPackets.channelJoinSuccess(userID, "#osu")) responseToken.enqueue(serverPackets.channelJoinSuccess(userID, "#announce")) # Output channels info for key, value in glob.channels.channels.items(): responseToken.enqueue(serverPackets.channelInfo(key)) responseToken.enqueue(serverPackets.friendList(userID)) # Send main menu icon and login notification if needed if (glob.banchoConf.config["menuIcon"] != ""): responseToken.enqueue(serverPackets.mainMenuIcon(glob.banchoConf.config["menuIcon"])) if (glob.banchoConf.config["loginNotification"] != ""): responseToken.enqueue(serverPackets.notification(glob.banchoConf.config["loginNotification"])) # Get everyone else userpanel # TODO: Better online users handling for key, value in glob.tokens.tokens.items(): responseToken.enqueue(serverPackets.userPanel(value.userID)) responseToken.enqueue(serverPackets.userStats(value.userID)) # Send online users IDs array responseToken.enqueue(serverPackets.onlineUsers()) # Send to everyone our userpanel and userStats (so they now we have logged in) glob.tokens.enqueueAll(serverPackets.userPanel(userID)) glob.tokens.enqueueAll(serverPackets.userStats(userID)) # Set position and country responseToken.setLocation(locationHelper.getLocation(requestIP)) responseToken.setCountry(countryHelper.getCountryID(locationHelper.getCountry(requestIP))) # Set reponse data to right value and reset our queue responseData = responseToken.queue responseToken.resetQueue() except exceptions.loginFailedException: # Login failed error packet # (we don't use enqueue because we don't have a token since login has failed) err = True responseData += serverPackets.loginFailed() except exceptions.loginBannedException: # Login banned error packet err = True responseData += serverPackets.loginBanned() except exceptions.banchoMaintenanceException: # Bancho is in maintenance mode responseData += serverPackets.notification("Our bancho server is in maintenance mode. Please try to login again later.") responseData += serverPackets.loginError() finally: # Print login failed message to console if needed if (err == True): consoleHelper.printColored("> "+loginData[0]+"'s login failed", bcolors.YELLOW) else: try: # This is not the first packet, send response based on client's request # Packet start position, used to read stacked packets pos = 0 # Make sure the token exists if (requestToken not in glob.tokens.tokens): raise exceptions.tokenNotFoundException() # Token exists, get its object userToken = glob.tokens.tokens[requestToken] # Get userID and username from token userID = userToken.userID username = userToken.username userRank = userToken.rank # Keep reading packets until everything has been read while pos < len(requestData): # Get packet from stack starting from new packet leftData = requestData[pos:] # Get packet ID, data length and data packetID = packetHelper.readPacketID(leftData) dataLength = packetHelper.readPacketLength(leftData) packetData = requestData[pos:(pos+dataLength+7)] # Console output if needed if (serverOutputPackets == True and packetID != 4): consoleHelper.printColored("Incoming packet ("+requestToken+")("+username+"):", bcolors.GREEN) consoleHelper.printColored("Packet code: "+str(packetID)+"\nPacket length: "+str(dataLength)+"\nSingle packet data: "+str(packetData)+"\n", bcolors.YELLOW) # Packet switch if (packetID == packetIDs.client_pong): # Ping packet, nothing to do # New packets are automatically taken from the queue pass elif (packetID == packetIDs.client_sendPublicMessage): try: # Public chat packet packetData = clientPackets.sendPublicMessage(packetData) # Receivers who = [] # Check #spectator if (packetData["to"] != "#spectator"): # Standard channel # Make sure the channel exists if (packetData["to"] not in glob.channels.channels): raise exceptions.channelUnknownException # Make sure the channel is not in moderated mode if (glob.channels.channels[packetData["to"]].moderated == True and userRank < 2): raise exceptions.channelModeratedException # Make sure we have write permissions if (glob.channels.channels[packetData["to"]].publicWrite == False and userRank < 2): raise exceptions.channelNoPermissionsException # Send this packet to everyone in that channel except us who = glob.channels.channels[packetData["to"]].getConnectedUsers()[:] if userID in who: who.remove(userID) else: # Spectator channel # Send this packet to every spectator and host if (userToken.spectating == 0): # We have sent to send a message to our #spectator channel targetToken = userToken who = targetToken.spectators[:] # No need to remove us because we are the host so we are not in spectators list else: # We have sent a message to someone else's #spectator targetToken = glob.tokens.getTokenFromUserID(userToken.spectating) who = targetToken.spectators[:] # Remove us if (userID in who): who.remove(userID) # Add host who.append(targetToken.userID) # Send packet to required users glob.tokens.multipleEnqueue(serverPackets.sendMessage(username, packetData["to"], packetData["message"]), who, False) # Fokabot command check fokaMessage = fokabot.fokabotResponse(username, packetData["to"], packetData["message"]) if (fokaMessage != False): who.append(userID) glob.tokens.multipleEnqueue(serverPackets.sendMessage("FokaBot", packetData["to"], fokaMessage), who, False) consoleHelper.printColored("> FokaBot@"+packetData["to"]+": "+str(fokaMessage.encode("UTF-8")), bcolors.PINK) # Console output consoleHelper.printColored("> "+username+"@"+packetData["to"]+": "+str(packetData["message"].encode("UTF-8")), bcolors.PINK) except exceptions.channelModeratedException: consoleHelper.printColored("[!] "+username+" has attempted to send a message to a channel that is in moderated mode ("+packetData["to"]+")", bcolors.RED) except exceptions.channelUnknownException: consoleHelper.printColored("[!] "+username+" has attempted to send a message to an unknown channel ("+packetData["to"]+")", bcolors.RED) except exceptions.channelNoPermissionsException: consoleHelper.printColored("[!] "+username+" has attempted to send a message to channel "+packetData["to"]+", but he has no write permissions", bcolors.RED) elif (packetID == packetIDs.client_sendPrivateMessage): try: # Private message packet packetData = clientPackets.sendPrivateMessage(packetData) if (packetData["to"] == "FokaBot"): # FokaBot command check fokaMessage = fokabot.fokabotResponse(username, packetData["to"], packetData["message"]) if (fokaMessage != False): userToken.enqueue(serverPackets.sendMessage("FokaBot", username, fokaMessage)) consoleHelper.printColored("> FokaBot>"+packetData["to"]+": "+str(fokaMessage.encode("UTF-8")), bcolors.PINK) else: # Send packet message to target if it exists token = glob.tokens.getTokenFromUsername(packetData["to"]) if (token == None): raise exceptions.tokenNotFoundException() token.enqueue(serverPackets.sendMessage(username, packetData["to"], packetData["message"])) # Console output consoleHelper.printColored("> "+username+">"+packetData["to"]+": "+packetData["message"], bcolors.PINK) except exceptions.tokenNotFoundException: # Token not found, user disconnected consoleHelper.printColored("[!] "+username+" has tried to send a message to "+packetData["to"]+", but its token couldn't be found", bcolors.RED) elif (packetID == packetIDs.client_channelJoin): try: # Channel join packet packetData = clientPackets.channelJoin(packetData) # Check spectator channel # If it's spectator channel, skip checks and list stuff if (packetData["channel"] != "#spectator"): # Normal channel, do check stuff # Make sure the channel exists if (packetData["channel"] not in glob.channels.channels): raise exceptions.channelUnknownException # Check channel permissions if ((glob.channels.channels[packetData["channel"]].publicWrite == False or glob.channels.channels[packetData["channel"]].moderated == True) and userRank < 2): raise exceptions.channelNoPermissionsException # Add our userID to users in that channel glob.channels.channels[packetData["channel"]].userJoin(userID) # Add the channel to our joined channel userToken.joinChannel(packetData["channel"]) # Send channel joined userToken.enqueue(serverPackets.channelJoinSuccess(userID, packetData["channel"])) # Console output consoleHelper.printColored("> "+username+" has joined channel "+packetData["channel"], bcolors.GREEN) except exceptions.channelNoPermissionsException: consoleHelper.printColored("[!] "+username+" has attempted to join channel "+packetData["channel"]+", but he has no read permissions", bcolors.RED) except exceptions.channelUnknownException: consoleHelper.printColored("[!] "+username+" has attempted to join an unknown channel ("+packetData["channel"]+")", bcolors.RED) elif (packetID == packetIDs.client_channelPart): # Channel part packet packetData = clientPackets.channelPart(packetData) # Remove us from joined users and joined channels if packetData["channel"] in glob.channels.channels: userToken.partChannel(packetData["channel"]) glob.channels.channels[packetData["channel"]].userPart(userID) # Console output consoleHelper.printColored("> "+username+" has parted channel "+packetData["channel"], bcolors.YELLOW) elif (packetID == packetIDs.client_changeAction): # Change action packet packetData = clientPackets.userActionChange(packetData) # Update our action id, text and md5 userToken.actionID = packetData["actionID"] userToken.actionText = packetData["actionText"] userToken.actionMd5 = packetData["actionMd5"] userToken.actionMods = packetData["actionMods"] userToken.gameMode = packetData["gameMode"] # Enqueue our new user panel and stats to everyone glob.tokens.enqueueAll(serverPackets.userPanel(userID)) glob.tokens.enqueueAll(serverPackets.userStats(userID)) # Console output print("> "+username+" has changed action: "+str(userToken.actionID)+" ["+userToken.actionText+"]["+userToken.actionMd5+"]") elif (packetID == packetIDs.client_startSpectating): try: # Start spectating packet packetData = clientPackets.startSpectating(packetData) # Stop spectating old user if needed if (userToken.spectating != 0): oldTargetToken = glob.tokens.getTokenFromUserID(userToken.spectating) oldTargetToken.enqueue(serverPackets.removeSpectator(userID)) userToken.stopSpectating() # Start spectating new user userToken.startSpectating(packetData["userID"]) # Get host token targetToken = glob.tokens.getTokenFromUserID(packetData["userID"]) if (targetToken == None): raise exceptions.tokenNotFoundException # Add us to host's spectators targetToken.addSpectator(userID) # Send spectator join packet to host targetToken.enqueue(serverPackets.addSpectator(userID)) # Join #spectator channel userToken.enqueue(serverPackets.channelJoinSuccess(userID, "#spectator")) if (len(targetToken.spectators) == 1): # First spectator, send #spectator join to host too targetToken.enqueue(serverPackets.channelJoinSuccess(userID, "#spectator")) # Console output consoleHelper.printColored("> "+username+" is spectating "+userHelper.getUserUsername(packetData["userID"]), bcolors.PINK) consoleHelper.printColored("> {}'s spectators: {}".format(str(packetData["userID"]), str(targetToken.spectators)), bcolors.BLUE) except exceptions.tokenNotFoundException: # Stop spectating if token not found consoleHelper.printColored("[!] Spectator start: token not found", bcolors.RED) userToken.stopSpectating() elif (packetID == packetIDs.client_stopSpectating): try: # Stop spectating packet, has no parameters # Remove our userID from host's spectators target = userToken.spectating targetToken = glob.tokens.getTokenFromUserID(target) if (targetToken == None): raise exceptions.tokenNotFoundException targetToken.removeSpectator(userID) # Send the spectator left packet to host targetToken.enqueue(serverPackets.removeSpectator(userID)) # Console output # TODO: Move messages in stop spectating consoleHelper.printColored("> "+username+" is no longer spectating whoever he was spectating", bcolors.PINK) consoleHelper.printColored("> {}'s spectators: {}".format(str(target), str(targetToken.spectators)), bcolors.BLUE) except exceptions.tokenNotFoundException: consoleHelper.printColored("[!] Spectator stop: token not found", bcolors.RED) finally: # Set our spectating user to 0 userToken.stopSpectating() elif (packetID == packetIDs.client_cantSpectate): try: # We don't have the beatmap, we can't spectate target = userToken.spectating targetToken = glob.tokens.getTokenFromUserID(target) # Send the packet to host targetToken.enqueue(serverPackets.noSongSpectator(userID)) except exceptions.tokenNotFoundException: # Stop spectating if token not found consoleHelper.printColored("[!] Spectator can't spectate: token not found", bcolors.RED) userToken.stopSpectating() elif (packetID == packetIDs.client_spectateFrames): # Client spectate frames # Send spectator frames to every spectator consoleHelper.printColored("> {}'s spectators: {}".format(str(userID), str(userToken.spectators)), bcolors.BLUE) for i in userToken.spectators: if (i != userID): # TODO: Check that spectators are spectating us # Send to every spectator but us (host) spectatorToken = glob.tokens.getTokenFromUserID(i) if (spectatorToken != None): # Token found, send frames spectatorToken.enqueue(serverPackets.spectatorFrames(packetData[7:])) else: # Token not found, remove it userToken.removeSpectator(i) userToken.enqueue(serverPackets.removeSpectator(i)) elif (packetID == packetIDs.client_friendAdd): # Friend add packet packetData = clientPackets.addRemoveFriend(packetData) userHelper.addFriend(userID, packetData["friendID"]) # Console output print("> "+username+" has added "+str(packetData["friendID"])+" to his friends") elif (packetID == packetIDs.client_friendRemove): # Friend remove packet packetData = clientPackets.addRemoveFriend(packetData) userHelper.removeFriend(userID, packetData["friendID"]) # Console output print("> "+username+" has removed "+str(packetData["friendID"])+" from his friends") elif (packetID == packetIDs.client_logout): # Logout packet, no parameters to read # Big client meme here. If someone logs out and logs in right after, # the old logout packet will still be in the queue and will be sent to # the server, so we accept logout packets sent at least 5 seconds after login if (int(time.time()-userToken.loginTime) >= 5): # TODO: Channel part at logout # TODO: Stop spectating at logout # TODO: Stop spectating at timeout # Enqueue our disconnection to everyone else glob.tokens.enqueueAll(serverPackets.userLogout(userID)) # Delete token glob.tokens.deleteToken(requestToken) consoleHelper.printColored("> "+username+" has been disconnected (logout)", bcolors.YELLOW) # Set reponse data and tokenstring to right value and reset our queue # Update pos so we can read the next stacked packet pos += dataLength+7 # add packet ID bytes, unused byte and data length bytes # WHILE END # Token queue built, send it # TODO: Move somewhere else responseTokenString = userToken.token responseData = userToken.queue userToken.resetQueue() # Update ping time for timeout userToken.updatePingTime() except exceptions.tokenNotFoundException: # Token not found. Disconnect that user responseData = serverPackets.loginError() responseData += serverPackets.notification("Whoops! Something went wrong, please login again.") consoleHelper.printColored("[!] Received packet from unknown token ("+requestToken+").", bcolors.RED) consoleHelper.printColored("> "+requestToken+" has been disconnected (invalid token)", bcolors.YELLOW) # Send server's response to client # We don't use token object because we might not have a token (failed login) return responseHelper.generateResponse(responseTokenString, responseData) else: # Not a POST request, send html page # TODO: Fix this crap return responseHelper.HTMLResponse()
def fokabotResponse(fro, chan, message): """ Check if a message has triggered fokabot (and return its response) fro -- sender username (for permissions stuff with admin commands) chan -- channel name message -- message return -- fokabot's response string or False """ if "!roll" in message: maxPoints = 100 message = message.split(" ") # Get max number if needed if (len(message) >= 2): if (message[1].isdigit() == True and int(message[1]) > 0): maxPoints = int(message[1]) points = random.randrange(0,maxPoints) return fro+" rolls "+str(points)+" points!" elif "!faq rules" in message: return "Please make sure to check (Ripple's rules)[http://ripple.moe/?p=23]." elif "!faq swearing" in message: return "Please don't abuse swearing" elif "!faq spam" in message: return "Please don't spam" elif "!faq offend" in message: return "Please offend other players" elif "!help" in message: return "Click (here)[https://ripple.moe/index.php?p=16&id=4] for full FokaBot's command list" elif "!report" in message: return "Report command is not here yet :(" # Admin commands elif "!moderated" in message: try: # Admin check if (userHelper.getUserRank(userHelper.getUserID(fro)) <= 1): raise exceptions.noAdminException # Make sure we are in a channel and not PM if (chan.startswith("#") == False): raise exceptions.moderatedPMException # Split message and default value message = message.lower().split(" ") enable = True # Get on/off if (len(message) >= 2): if (message[1] == "off"): enable = False # Turn on/off moderated mode glob.channels.channels[chan].moderated = enable return "This channel is {} in moderated mode!".format("now" if enable else "no longer") except exceptions.noAdminException: consoleHelper.printColored("[!] "+fro+" has tried to put "+chan+" in moderated mode, but he is not an admin.", bcolors.RED) return False except exceptions.moderatedPMException: consoleHelper.printColored("[!] "+fro+" has tried to put a PM chat in moderated mode.", bcolors.RED) return "Are you trying to put a private chat in moderated mode? Are you serious?!? You're fired." elif "!system" in message: # System commands try: # Admin check if (userHelper.getUserRank(userHelper.getUserID(fro)) <= 1): raise exceptions.noAdminException # Split message message = message.lower().split(" ") # Get parameters if (len(message) >= 2): if (message[1] == "restart"): msg = "We are performing some maintenance. Bancho will restart in 5 seconds. Thank you for your patience." systemHelper.scheduleShutdown(5, True, msg) return msg elif (message[1] == "status"): # Print some server info data = systemHelper.getSystemInfo() # Final message msg = "=== PEP.PY STATS ===\n" msg += "Running pep.py server\n" msg += "Webserver: "+data["webServer"]+"\n" msg += "\n" msg += "=== BANCHO STATS ===\n" msg += "Connected users: "+str(data["connectedUsers"])+"\n" msg += "\n" msg += "=== SYSTEM STATS ===\n" msg += "CPU: "+str(data["cpuUsage"])+"%\n" msg += "RAM: "+str(data["usedMemory"])+"GB/"+str(data["totalMemory"])+"GB\n" if (data["unix"] == True): msg += "Load average: "+str(data["loadAverage"][0])+"/"+str(data["loadAverage"][1])+"/"+str(data["loadAverage"][2])+"\n" return msg elif (message[1] == "reload"): #Reload settings from bancho_settings glob.banchoConf.loadSettings() return "Bancho settings reloaded!" elif (message[1] == "maintenance"): # Turn on/off bancho maintenance maintenance = True # Get on/off if (len(message) >= 2): if (message[2] == "off"): maintenance = False # Set new maintenance value in bancho_settings table glob.banchoConf.setMaintenance(maintenance) if (maintenance == True): # We have turned on maintenance mode # Users that will be disconnected who = [] # Disconnect everyone but mod/admins for key,value in glob.tokens.tokens.items(): if (value.rank <= 2): who.append(value.userID) glob.tokens.enqueueAll(serverPackets.notification("Our bancho server is in maintenance mode. Please try to login again later.")) glob.tokens.multipleEnqueue(serverPackets.loginError(), who) msg = "The server is now in maintenance mode!" else: # We have turned off maintenance mode # Send message if we have turned off maintenance mode msg = "The server is no longer in maintenance mode!" # Chat output return msg else: raise exceptions.commandSyntaxException except exceptions.noAdminException: consoleHelper.printColored("[!] "+fro+" has tried to run a system command, but he is not an admin.", bcolors.RED) return False except exceptions.commandSyntaxException: consoleHelper.printColored("[!] Fokabot command syntax error", bcolors.RED) return False elif "!scareall" in message: try: # Admin check if (userHelper.getUserRank(userHelper.getUserID(fro)) <= 1): raise exceptions.noAdminException # Get parameters message = message.lower().split(" ") if (len(message) < 2): raise exceptions.commandSyntaxException scaryMessage = ' '.join(message[1:]) # Send packet to everyone consoleHelper.printColored("> {} is turning osu! into an horror game ({})".format(fro, scaryMessage), bcolors.PINK) glob.tokens.enqueueAll(serverPackets.jumpscare(scaryMessage)) except exceptions.noAdminException: pass except exceptions.commandSyntaxException: return "Wrong syntax. !scareall <message>" finally: # No respobnse return False elif "!scare" in message: try: # Admin check if (userHelper.getUserRank(userHelper.getUserID(fro)) <= 1): raise exceptions.noAdminException # Get parameters message = message.lower().split(" ") if (len(message) < 3): raise exceptions.commandSyntaxException target = message[1] scaryMessage = ' '.join(message[2:]) # Get target token and make sure is connected targetToken = glob.tokens.getTokenFromUsername(target) if (targetToken == None): raise exceptions.tokenNotFoundException # Send packet to target consoleHelper.printColored("> Rip {}'s heart ({}). ~ <3, {}".format(target, scaryMessage, fro), bcolors.PINK) targetToken.enqueue(serverPackets.jumpscare(scaryMessage)) # No response return False except exceptions.noAdminException: return False except exceptions.tokenNotFoundException: return "{} is not online".format(message[1]) except exceptions.commandSyntaxException: return "Wrong syntax. !scare <target> <message>" elif "!kick" in message: try: # Admin check # TODO: God this sucks if (userHelper.getUserRank(userHelper.getUserID(fro)) <= 1): raise exceptions.noAdminException # Get parameters message = message.lower().split(" ") if (len(message) < 2): raise exceptions.commandSyntaxException target = message[1] # Get target token and make sure is connected targetToken = glob.tokens.getTokenFromUsername(target) if (targetToken == None): raise exceptions.tokenNotFoundException # Send packet to target consoleHelper.printColored("> {} has been disconnected. (kick)".format(target), bcolors.YELLOW) targetToken.enqueue(serverPackets.notification("You have been kicked from the server. Please login again.")) targetToken.enqueue(serverPackets.loginFailed()) # Bot response return "{} has been kicked from the server.".format(message[1]) except exceptions.noAdminException: return False except exceptions.tokenNotFoundException: return "{} is not online.".format(message[1]) except exceptions.commandSyntaxException: return "Wrong syntax. !kick <target>" elif "!silence" in message: try: # Admin check if (userHelper.getUserRank(userHelper.getUserID(fro)) <= 1): raise exceptions.noAdminException # Get parameters message = message.lower().split(" ") if (len(message) < 4): raise exceptions.commandSyntaxException target = message[1] amount = message[2] unit = message[3] reason = ' '.join(message[4:]) # Get target user ID targetUserID = userHelper.getUserID(target) # Make sure the user exists if (targetUserID == False): raise exceptions.userNotFoundException # Calculate silence seconds if (unit == 's'): silenceTime = int(amount) elif (unit == 'm'): silenceTime = int(amount)*60 elif (unit == 'h'): silenceTime = int(amount)*3600 elif (unit == 'd'): silenceTime = int(amount)*86400 else: raise exceptions.commandSyntaxException # Max silence time is 7 days if (silenceTime > 604800): raise exceptions.commandSyntaxException # Calculate silence end time endTime = int(time.time())+silenceTime # Update silence end in db userHelper.silenceUser(targetUserID, endTime, reason) # Check if target is connected targetToken = glob.tokens.getTokenFromUsername(target) if (targetToken == None): tokenFound = False else: tokenFound = True # Send silence packets if user is online if (tokenFound == True): targetToken.enqueue(serverPackets.silenceEndTime(silenceTime)) consoleHelper.printColored("{} has been silenced for {} seconds the following reason: {}".format(target, silenceTime, reason), bcolors.PINK) # Bot response return "{} has been silenced for the following reason: {}".format(target, reason) except exceptions.userNotFoundException: return "{}: user not found".format(message[1]) except exceptions.noAdminException: return False except exceptions.commandSyntaxException: return "Wrong syntax. !silence <target> <amount> <unit (s/m/h/d)> <reason>. Max silence time is 7 days." elif "!removesilence" in message or "!resetsilence" in message: try: # Admin check if (userHelper.getUserRank(userHelper.getUserID(fro)) <= 1): raise exceptions.noAdminException # Get parameters message = message.lower().split(" ") if (len(message) < 2): raise exceptions.commandSyntaxException target = message[1] # Make sure the user exists targetUserID = userHelper.getUserID(target) if (targetUserID == False): raise exceptions.userNotFoundException # Reset user silence time and reason in db userHelper.silenceUser(targetUserID, 0, "") # Send new silence end packet to user if he's online targetToken = glob.tokens.getTokenFromUsername(target) if (targetToken != None): targetToken.enqueue(serverPackets.silenceEndTime(0)) return "{}'s silence reset".format(target) except exceptions.commandSyntaxException: return "Wrong syntax. !removesilence <target>" except exceptions.noAdminException: return False except exceptions.userNotFoundException: return "{}: user not found".format(message[1]) elif "!fokabot reconnect" in message: try: # Admin check if (userHelper.getUserRank(userHelper.getUserID(fro)) <= 1): raise exceptions.noAdminException # Check if fokabot is already connected if (glob.tokens.getTokenFromUserID(999) != None): raise exceptions.alreadyConnectedException # Fokabot is not connected, connect it connect() return False except exceptions.noAdminException: return False except exceptions.alreadyConnectedException: return "Fokabot is already connected to Bancho" else: return False
def banchoServer(): if (flask.request.method == 'POST'): # Track time if needed if (serverOutputRequestTime == True): # Start time st = datetime.datetime.now() # Client's token string and request data requestTokenString = flask.request.headers.get('osu-token') requestData = flask.request.data # Server's token string and request data responseTokenString = "ayy" responseData = bytes() if (requestTokenString == None): # No token, first request. Handle login. responseTokenString, responseData = loginEvent.handle( flask.request) else: try: # This is not the first packet, send response based on client's request # Packet start position, used to read stacked packets pos = 0 # Make sure the token exists if (requestTokenString not in glob.tokens.tokens): raise exceptions.tokenNotFoundException() # Token exists, get its object userToken = glob.tokens.tokens[requestTokenString] # Keep reading packets until everything has been read while pos < len(requestData): # Get packet from stack starting from new packet leftData = requestData[pos:] # Get packet ID, data length and data packetID = packetHelper.readPacketID(leftData) dataLength = packetHelper.readPacketLength(leftData) packetData = requestData[pos:(pos + dataLength + 7)] # Console output if needed if (serverOutputPackets == True and packetID != 4): consoleHelper.printColored( "Incoming packet ({})({}):".format( requestTokenString, userToken.username), bcolors.GREEN) consoleHelper.printColored( "Packet code: {}\nPacket length: {}\nSingle packet data: {}\n" .format(str(packetID), str(dataLength), str(packetData)), bcolors.YELLOW) # Event handler def handleEvent(ev): def wrapper(): ev.handle(userToken, packetData) return wrapper eventHandler = { # TODO: Rename packets and events # TODO: Host check for multi packetIDs.client_sendPublicMessage: handleEvent(sendPublicMessageEvent), packetIDs.client_sendPrivateMessage: handleEvent(sendPrivateMessageEvent), packetIDs.client_setAwayMessage: handleEvent(setAwayMessageEvent), packetIDs.client_channelJoin: handleEvent(channelJoinEvent), packetIDs.client_channelPart: handleEvent(channelPartEvent), packetIDs.client_changeAction: handleEvent(changeActionEvent), packetIDs.client_startSpectating: handleEvent(startSpectatingEvent), packetIDs.client_stopSpectating: handleEvent(stopSpectatingEvent), packetIDs.client_cantSpectate: handleEvent(cantSpectateEvent), packetIDs.client_spectateFrames: handleEvent(spectateFramesEvent), packetIDs.client_friendAdd: handleEvent(friendAddEvent), packetIDs.client_friendRemove: handleEvent(friendRemoveEvent), packetIDs.client_logout: handleEvent(logoutEvent), packetIDs.client_joinLobby: handleEvent(joinLobbyEvent), packetIDs.client_partLobby: handleEvent(partLobbyEvent), packetIDs.client_createMatch: handleEvent(createMatchEvent), packetIDs.client_joinMatch: handleEvent(joinMatchEvent), packetIDs.client_partMatch: handleEvent(partMatchEvent), packetIDs.client_matchChangeSlot: handleEvent(changeSlotEvent), packetIDs.client_matchChangeSettings: handleEvent(changeMatchSettingsEvent), packetIDs.client_matchChangePassword: handleEvent(changeMatchPasswordEvent), packetIDs.client_matchChangeMods: handleEvent(changeMatchModsEvent), packetIDs.client_matchReady: handleEvent(matchReadyEvent), packetIDs.client_matchNotReady: handleEvent(matchReadyEvent), packetIDs.client_matchLock: handleEvent(matchLockEvent), packetIDs.client_matchStart: handleEvent(matchStartEvent), packetIDs.client_matchLoadComplete: handleEvent(matchPlayerLoadEvent), packetIDs.client_matchSkipRequest: handleEvent(matchSkipEvent), packetIDs.client_matchScoreUpdate: handleEvent(matchFramesEvent), packetIDs.client_matchComplete: handleEvent(matchCompleteEvent), packetIDs.client_matchNoBeatmap: handleEvent(matchNoBeatmapEvent), packetIDs.client_matchHasBeatmap: handleEvent(matchHasBeatmapEvent), packetIDs.client_matchTransferHost: handleEvent(matchTransferHostEvent), packetIDs.client_matchFailed: handleEvent(matchFailedEvent), packetIDs.client_invite: handleEvent(matchInviteEvent), packetIDs.client_matchChangeTeam: handleEvent(matchChangeTeamEvent) } if packetID != 4: if packetID in eventHandler: eventHandler[packetID]() else: consoleHelper.printColored( "[!] Unknown packet id from {} ({})".format( requestTokenString, packetID), bcolors.RED) # Update pos so we can read the next stacked packet # +7 because we add packet ID bytes, unused byte and data length bytes pos += dataLength + 7 # Token queue built, send it responseTokenString = userToken.token responseData = userToken.queue userToken.resetQueue() # Update ping time for timeout userToken.updatePingTime() except exceptions.tokenNotFoundException: # Token not found. Disconnect that user responseData = serverPackets.loginError() responseData += serverPackets.notification( "Whoops! Something went wrong, please login again.") consoleHelper.printColored( "[!] Received packet from unknown token ({}).".format( requestTokenString), bcolors.RED) consoleHelper.printColored( "> {} have been disconnected (invalid token)".format( requestTokenString), bcolors.YELLOW) if (serverOutputRequestTime == True): # End time et = datetime.datetime.now() # Total time: tt = float((et.microsecond - st.microsecond) / 1000) consoleHelper.printColored("Request time: {}ms".format(tt), bcolors.PINK) # Send server's response to client # We don't use token object because we might not have a token (failed login) return responseHelper.generateResponse(responseTokenString, responseData) else: # Not a POST request, send html page return responseHelper.HTMLResponse()
def fokabotResponse(fro, chan, message): """ Check if a message has triggered fokabot (and return its response) fro -- sender username (for permissions stuff with admin commands) chan -- channel name message -- message return -- fokabot's response string or False """ if "!roll" in message: maxPoints = 100 message = message.split(" ") # Get max number if needed if (len(message) >= 2): if (message[1].isdigit() == True and int(message[1]) > 0): maxPoints = int(message[1]) points = random.randrange(0,maxPoints) return "{} rolls {} points!".format(fro, str(points)) elif "!faq rules" in message: return "Please make sure to check (Ripple's rules)[http://ripple.moe/?p=23]." elif "!faq swearing" in message: return "Please don't abuse swearing" elif "!faq spam" in message: return "Please don't spam" elif "!faq offend" in message: return "Please don't offend other players" elif "!help" in message: return "Click (here)[https://ripple.moe/index.php?p=16&id=4] for FokaBot's full command list" elif "!report" in message: return "Report command is not here, yet :(" elif "!faq github" in message: return "(Ripple's Github page!)[https://github.com/osuripple/ripple]" elif "!faq discord" in message: return "(Join Ripple's Discord!)[https://discord.gg/0rJcZruIsA6rXuIx]" elif "!faq blog" in message: return "You can find the latest Ripple news on the (blog)[https://ripple.moe/blog/]!" elif "!faq changelog" in message: return "Check the (changelog)[https://ripple.moe/index.php?p=17] !" elif "!ask" in message: if (len(message.split(" ")) >= 2): return "{}: {}".format(fro, random.choice(["Yes", "No", "Maybe"])) else: return False # Admin commands elif "!moderated" in message: try: # Admin check if (userHelper.getUserRank(userHelper.getUserID(fro)) <= 1): raise exceptions.noAdminException # Make sure we are in a channel and not PM if (chan.startswith("#") == False): raise exceptions.moderatedPMException # Split message and default value message = message.lower().split(" ") enable = True # Get on/off if (len(message) >= 2): if (message[1] == "off"): enable = False # Turn on/off moderated mode glob.channels.channels[chan].moderated = enable return "This channel is {} in moderated mode!".format("now" if enable else "no longer") except exceptions.noAdminException: consoleHelper.printColored("[!] {} tried to put {} in moderated mode, but they are not an admin.".format(fro, chan), bcolors.RED) return False except exceptions.moderatedPMException: consoleHelper.printColored("[!] {} tried to put a PM chat in moderated mode.".format(fro), bcolors.RED) return "You are trying to put a private chat in moderated mode. Are you serious?!? You're fired." elif "!system" in message: # System commands try: # Admin check if (userHelper.getUserRank(userHelper.getUserID(fro)) <= 1): raise exceptions.noAdminException # Split message message = message.lower().split(" ") # Get parameters if (len(message) >= 2): if (message[1] == "restart" or message[1] == "shutdown"): restart = message[1] == "restart" msg = "We are performing some maintenance. Bancho will {} in 5 seconds. Thank you for your patience.".format("restart" if restart else "shutdown") systemHelper.scheduleShutdown(5, restart, msg) return msg elif (message[1] == "status"): # Print some server info data = systemHelper.getSystemInfo() # Final message msg = "=== PEP.PY STATS ===\n" msg += "Running pep.py server\n" msg += "Webserver: {}\n".format(data["webServer"]) msg += "\n" msg += "=== BANCHO STATS ===\n" msg += "Connected users: {}\n".format(str(data["connectedUsers"])) msg += "\n" msg += "=== SYSTEM STATS ===\n" msg += "CPU: {}%\n".format(str(data["cpuUsage"])) msg += "RAM: {}GB/{}GB\n".format(str(data["usedMemory"]), str(data["totalMemory"])) if (data["unix"] == True): msg += "Load average: {}/{}/{}\n".format(str(data["loadAverage"][0]), str(data["loadAverage"][1]), str(data["loadAverage"][2])) return msg elif (message[1] == "reload"): #Reload settings from bancho_settings glob.banchoConf.loadSettings() # Reload channels too glob.channels.loadChannels() # Send new channels and new bottom icon to everyone glob.tokens.enqueueAll(serverPackets.channelInfoEnd()) for key, value in glob.channels.channels.items(): glob.tokens.enqueueAll(serverPackets.channelInfo(key)) glob.tokens.enqueueAll(serverPackets.mainMenuIcon(glob.banchoConf.config["menuIcon"])) return "Bancho settings reloaded!" elif (message[1] == "maintenance"): # Turn on/off bancho maintenance maintenance = True # Get on/off if (len(message) >= 2): if (message[2] == "off"): maintenance = False # Set new maintenance value in bancho_settings table glob.banchoConf.setMaintenance(maintenance) if (maintenance == True): # We have turned on maintenance mode # Users that will be disconnected who = [] # Disconnect everyone but mod/admins for _, value in glob.tokens.tokens.items(): if (value.rank <= 2): who.append(value.userID) glob.tokens.enqueueAll(serverPackets.notification("Our bancho server is in maintenance mode. Please try to login again later.")) glob.tokens.multipleEnqueue(serverPackets.loginError(), who) msg = "The server is now in maintenance mode!" else: # We have turned off maintenance mode # Send message if we have turned off maintenance mode msg = "The server is no longer in maintenance mode!" # Chat output return msg else: # Command not found return False else: raise exceptions.commandSyntaxException except exceptions.noAdminException: consoleHelper.printColored("[!] {} tried to run a system command, but they are not an admin.".format(fro), bcolors.RED) return False except exceptions.commandSyntaxException: consoleHelper.printColored("[!] Fokabot command syntax error", bcolors.RED) return False elif "!scareall" in message: return False # !scareall is currently disabled. Remove this line to activate it. try: # Admin check if (userHelper.getUserRank(userHelper.getUserID(fro)) <= 1): raise exceptions.noAdminException # Get parameters message = message.lower().split(" ") if (len(message) < 2): raise exceptions.commandSyntaxException scaryMessage = ' '.join(message[1:]) # Send packet to everyone consoleHelper.printColored("> {} is turning osu! into an horror game ({})".format(fro, scaryMessage), bcolors.PINK) glob.tokens.enqueueAll(serverPackets.jumpscare(scaryMessage)) except exceptions.noAdminException: pass except exceptions.commandSyntaxException: return "Wrong syntax. !scareall <message>" finally: # No respobnse return False elif "!scare" in message: return False # !scare is currently disabled. Remove this line to activate it. try: # Admin check if (userHelper.getUserRank(userHelper.getUserID(fro)) <= 1): raise exceptions.noAdminException # Get parameters message = message.lower().split(" ") if (len(message) < 3): raise exceptions.commandSyntaxException target = message[1].replace("_", " ") scaryMessage = ' '.join(message[2:]) # Get target token and make sure is connected targetToken = glob.tokens.getTokenFromUsername(target) if (targetToken == None): raise exceptions.tokenNotFoundException # Send packet to target consoleHelper.printColored("> Rip {}'s heart ({}). ~ <3, {}".format(target, scaryMessage, fro), bcolors.PINK) targetToken.enqueue(serverPackets.jumpscare(scaryMessage)) # No response return False except exceptions.noAdminException: return False except exceptions.tokenNotFoundException: return "{} is not online".format(message[1]) except exceptions.commandSyntaxException: return "Wrong syntax. !scare <target> <message>" elif "!kickall" in message: try: # Check admin if (userHelper.getUserRank(userHelper.getUserID(fro)) <= 1): raise exceptions.noAdminException # Kick everyone but mods/admins toKick = [] for key, value in glob.tokens.tokens.items(): if (value.rank < 3): toKick.append(key) # Loop though users to kick (we can't change dictionary size while iterating) for i in toKick: if (i in glob.tokens.tokens): glob.tokens.tokens[i].kick() return "Whoops! Rip everyone." except exceptions.noAdminException: return False elif "!kick" in message: try: # Admin check if (userHelper.getUserRank(userHelper.getUserID(fro)) <= 1): raise exceptions.noAdminException # Get parameters message = message.lower().split(" ") if (len(message) < 2): raise exceptions.commandSyntaxException target = message[1].replace("_", " ") # Get target token and make sure is connected targetToken = glob.tokens.getTokenFromUsername(target) if (targetToken == None): raise exceptions.tokenNotFoundException # Kick user targetToken.kick() # Bot response return "{} has been kicked from the server.".format(message[1]) except exceptions.noAdminException: return False except exceptions.tokenNotFoundException: return "{} is not online.".format(message[1]) except exceptions.commandSyntaxException: return "Wrong syntax. !kick <target>" elif "!silence" in message: try: # Admin check if (userHelper.getUserRank(userHelper.getUserID(fro)) <= 1): raise exceptions.noAdminException # Get parameters message = message.lower().split(" ") if (len(message) < 4): raise exceptions.commandSyntaxException target = message[1].replace("_", " ") amount = message[2] unit = message[3] reason = ' '.join(message[4:]) # Get target user ID targetUserID = userHelper.getUserID(target) # Make sure the user exists if (targetUserID == False): raise exceptions.userNotFoundException # Calculate silence seconds if (unit == 's'): silenceTime = int(amount) elif (unit == 'm'): silenceTime = int(amount)*60 elif (unit == 'h'): silenceTime = int(amount)*3600 elif (unit == 'd'): silenceTime = int(amount)*86400 else: raise exceptions.commandSyntaxException # Max silence time is 7 days if (silenceTime > 604800): raise exceptions.commandSyntaxException # Calculate silence end time endTime = int(time.time())+silenceTime # Update silence end in db userHelper.silenceUser(targetUserID, endTime, reason) # Check if target is connected targetToken = glob.tokens.getTokenFromUsername(target) if (targetToken == None): tokenFound = False else: tokenFound = True # Send silence packets if user is online if (tokenFound == True): targetToken.enqueue(serverPackets.silenceEndTime(silenceTime)) consoleHelper.printColored("{} has been silenced for {} seconds the following reason: {}".format(target, silenceTime, reason), bcolors.PINK) # Bot response return "{} has been silenced for the following reason: {}".format(target, reason) except exceptions.userNotFoundException: return "{}: user not found".format(message[1]) except exceptions.noAdminException: return False except exceptions.commandSyntaxException: return "Wrong syntax. !silence <target> <amount> <unit (s/m/h/d)> <reason>. Max silence time is 7 days." elif "!removesilence" in message or "!resetsilence" in message: try: # Admin check if (userHelper.getUserRank(userHelper.getUserID(fro)) <= 1): raise exceptions.noAdminException # Get parameters message = message.lower().split(" ") if (len(message) < 2): raise exceptions.commandSyntaxException target = message[1].replace("_", " ") # Make sure the user exists targetUserID = userHelper.getUserID(target) if (targetUserID == False): raise exceptions.userNotFoundException # Reset user silence time and reason in db userHelper.silenceUser(targetUserID, 0, "") # Send new silence end packet to user if he's online targetToken = glob.tokens.getTokenFromUsername(target) if (targetToken != None): targetToken.enqueue(serverPackets.silenceEndTime(0)) return "{}'s silence reset".format(target) except exceptions.commandSyntaxException: return "Wrong syntax. !removesilence <target>" except exceptions.noAdminException: return False except exceptions.userNotFoundException: return "{}: user not found".format(message[1]) elif "!fokabot reconnect" in message: try: # Admin check if (userHelper.getUserRank(userHelper.getUserID(fro)) <= 1): raise exceptions.noAdminException # Check if fokabot is already connected if (glob.tokens.getTokenFromUserID(999) != None): raise exceptions.alreadyConnectedException # Fokabot is not connected, connect it connect() return False except exceptions.noAdminException: return False except exceptions.alreadyConnectedException: return "Fokabot is already connected to Bancho" elif "!alert" in message: try: # Admin check if (userHelper.getUserRank(userHelper.getUserID(fro)) <= 1): raise exceptions.noAdminException # Syntax check message = message.split(" ") if (len(message) < 2): raise exceptions.commandSyntaxException # Send alert to everyone glob.tokens.enqueueAll(serverPackets.notification(' '.join(message[1:]))) return False except exceptions.noAdminException: return False except exceptions.commandSyntaxException: return "Wrong syntax. !alert <message>" else: return False