pep.py/pep.py

366 lines
14 KiB
Python

"""Hello, pep.py here, ex-owner of ripple and prime minister of Ripwot."""
import logging
import sys
import flask
import datetime
import os
# Tornado server
from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
# pep.py files
from constants import bcolors
from constants import packetIDs
from constants import serverPackets
from helpers import configHelper
from helpers import discordBotHelper
from constants import exceptions
from objects import glob
from objects import fokabot
from objects import banchoConfig
from events import sendPublicMessageEvent
from events import sendPrivateMessageEvent
from events import channelJoinEvent
from events import channelPartEvent
from events import changeActionEvent
from events import cantSpectateEvent
from events import startSpectatingEvent
from events import stopSpectatingEvent
from events import spectateFramesEvent
from events import friendAddEvent
from events import friendRemoveEvent
from events import logoutEvent
from events import loginEvent
from events import setAwayMessageEvent
from events import joinLobbyEvent
from events import createMatchEvent
from events import partLobbyEvent
from events import changeSlotEvent
from events import joinMatchEvent
from events import partMatchEvent
from events import changeMatchSettingsEvent
from events import changeMatchPasswordEvent
from events import changeMatchModsEvent
from events import matchReadyEvent
from events import matchLockEvent
from events import matchStartEvent
from events import matchPlayerLoadEvent
from events import matchSkipEvent
from events import matchFramesEvent
from events import matchCompleteEvent
from events import matchNoBeatmapEvent
from events import matchHasBeatmapEvent
from events import matchTransferHostEvent
from events import matchFailedEvent
from events import matchInviteEvent
from events import matchChangeTeamEvent
# pep.py helpers
from helpers import packetHelper
from helpers import consoleHelper
from helpers import databaseHelper
from helpers import responseHelper
from helpers import generalFunctions
from helpers import systemHelper
# Create flask instance
app = flask.Flask(__name__)
# Get flask logger
flaskLogger = logging.getLogger("werkzeug")
# Ci trigger
@app.route("/ci-trigger")
@app.route("/api/ci-trigger")
def ciTrigger():
# Ci restart trigger
# Get ket from GET
key = flask.request.args.get('k')
# Get request ip
requestIP = flask.request.headers.get('X-Real-IP')
if requestIP == None:
requestIP = flask.request.remote_addr
# Check key
if key is None or key != glob.conf.config["ci"]["key"]:
consoleHelper.printColored("[!] Invalid ci trigger from {}".format(requestIP), bcolors.RED)
return flask.jsonify({"response" : "-1"})
# Ci event triggered, schedule server shutdown
consoleHelper.printColored("[!] Ci event triggered from {}".format(requestIP), bcolors.PINK)
systemHelper.scheduleShutdown(5, False, "A new Bancho update is available and the server will be restarted in 5 seconds. Thank you for your patience.")
return flask.jsonify({"response" : 1})
@app.route("/api/server-status")
def serverStatus():
# Server status api
# 1: Online
# -1: Restarting
return flask.jsonify({
"response" : 200,
"status" : -1 if glob.restarting == True else 1
})
# Main bancho server
@app.route("/", methods=['GET', 'POST'])
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()
if __name__ == "__main__":
# Server start
consoleHelper.printServerStartHeader(True)
# Read config.ini
consoleHelper.printNoNl("> Loading config file... ")
glob.conf = configHelper.config("config.ini")
if glob.conf.default == True:
# We have generated a default config.ini, quit server
consoleHelper.printWarning()
consoleHelper.printColored("[!] config.ini not found. A default one has been generated.", bcolors.YELLOW)
consoleHelper.printColored("[!] Please edit your config.ini and run the server again.", bcolors.YELLOW)
sys.exit()
# If we haven't generated a default config.ini, check if it's valid
if glob.conf.checkConfig() == False:
consoleHelper.printError()
consoleHelper.printColored("[!] Invalid config.ini. Please configure it properly", bcolors.RED)
consoleHelper.printColored("[!] Delete your config.ini to generate a default one", bcolors.RED)
sys.exit()
else:
consoleHelper.printDone()
# Connect to db
try:
consoleHelper.printNoNl("> Connecting to MySQL db... ")
glob.db = databaseHelper.db(glob.conf.config["db"]["host"], glob.conf.config["db"]["username"], glob.conf.config["db"]["password"], glob.conf.config["db"]["database"], int(glob.conf.config["db"]["pingtime"]))
consoleHelper.printDone()
except:
# Exception while connecting to db
consoleHelper.printError()
consoleHelper.printColored("[!] Error while connection to database. Please check your config.ini and run the server again", bcolors.RED)
raise
# Load bancho_settings
try:
consoleHelper.printNoNl("> Loading bancho settings from DB... ")
glob.banchoConf = banchoConfig.banchoConfig()
consoleHelper.printDone()
except:
consoleHelper.printError()
consoleHelper.printColored("[!] Error while loading bancho_settings. Please make sure the table in DB has all the required rows", bcolors.RED)
raise
# Create data folder if needed
consoleHelper.printNoNl("> Checking folders... ")
paths = [".data"]
for i in paths:
if not os.path.exists(i):
os.makedirs(i, 0o770)
consoleHelper.printDone()
# Initialize chat channels
consoleHelper.printNoNl("> Initializing chat channels... ")
glob.channels.loadChannels()
consoleHelper.printDone()
# Start fokabot
consoleHelper.printNoNl("> Connecting FokaBot... ")
fokabot.connect()
consoleHelper.printDone()
# Initialize user timeout check loop
try:
consoleHelper.printNoNl("> Initializing user timeout check loop... ")
glob.tokens.usersTimeoutCheckLoop(int(glob.conf.config["server"]["timeouttime"]), int(glob.conf.config["server"]["timeoutlooptime"]))
consoleHelper.printDone()
except:
consoleHelper.printError()
consoleHelper.printColored("[!] Error while initializing user timeout check loop", bcolors.RED)
consoleHelper.printColored("[!] Make sure that 'timeouttime' and 'timeoutlooptime' in config.ini are numbers", bcolors.RED)
raise
# Localize warning
if(generalFunctions.stringToBool(glob.conf.config["server"]["localizeusers"]) == False):
consoleHelper.printColored("[!] Warning! users localization is disabled!", bcolors.YELLOW)
# Get server parameters from config.ini
serverName = glob.conf.config["server"]["server"]
serverHost = glob.conf.config["server"]["host"]
serverPort = int(glob.conf.config["server"]["port"])
serverOutputPackets = generalFunctions.stringToBool(glob.conf.config["server"]["outputpackets"])
serverOutputRequestTime = generalFunctions.stringToBool(glob.conf.config["server"]["outputrequesttime"])
# Send server start message
discordBotHelper.sendConfidential("w00t p00t! (pep.py started)")
# Run server sanic way
if serverName == "tornado":
# Tornado server
consoleHelper.printColored("> Tornado listening for clients on 127.0.0.1:{}...".format(serverPort), bcolors.GREEN)
webServer = HTTPServer(WSGIContainer(app))
webServer.listen(serverPort)
IOLoop.instance().start()
elif serverName == "flask":
# Flask server
# Get flask settings
flaskThreaded = generalFunctions.stringToBool(glob.conf.config["flask"]["threaded"])
flaskDebug = generalFunctions.stringToBool(glob.conf.config["flask"]["debug"])
flaskLoggerStatus = not generalFunctions.stringToBool(glob.conf.config["flask"]["logger"])
# Set flask debug mode and logger
app.debug = flaskDebug
flaskLogger.disabled = flaskLoggerStatus
# Console output
if flaskDebug == False:
consoleHelper.printColored("> Flask listening for clients on {}.{}...".format(serverHost, serverPort), bcolors.GREEN)
else:
consoleHelper.printColored("> Flask "+bcolors.YELLOW+"(debug mode)"+bcolors.ENDC+" listening for clients on {}:{}...".format(serverHost, serverPort), bcolors.GREEN)
# Run flask server
app.run(host=serverHost, port=serverPort, threaded=flaskThreaded)
else:
print(bcolors.RED+"[!] Unknown server. Please set the server key in config.ini to "+bcolors.ENDC+bcolors.YELLOW+"tornado"+bcolors.ENDC+bcolors.RED+" or "+bcolors.ENDC+bcolors.YELLOW+"flask"+bcolors.ENDC)
sys.exit()