Moved pep.py to another repo
This commit is contained in:
commit
47305d612a
BIN
__pycache__/actions.cpython-35.pyc
Normal file
BIN
__pycache__/actions.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/banchoConfig.cpython-35.pyc
Normal file
BIN
__pycache__/banchoConfig.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/bcolors.cpython-35.pyc
Normal file
BIN
__pycache__/bcolors.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/cantSpectateEvent.cpython-35.pyc
Normal file
BIN
__pycache__/cantSpectateEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/changeActionEvent.cpython-35.pyc
Normal file
BIN
__pycache__/changeActionEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/changeMatchModsEvent.cpython-35.pyc
Normal file
BIN
__pycache__/changeMatchModsEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/changeMatchPasswordEvent.cpython-35.pyc
Normal file
BIN
__pycache__/changeMatchPasswordEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/changeMatchSettingsEvent.cpython-35.pyc
Normal file
BIN
__pycache__/changeMatchSettingsEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/changeSlotEvent.cpython-35.pyc
Normal file
BIN
__pycache__/changeSlotEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/channel.cpython-35.pyc
Normal file
BIN
__pycache__/channel.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/channelJoinEvent.cpython-35.pyc
Normal file
BIN
__pycache__/channelJoinEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/channelList.cpython-35.pyc
Normal file
BIN
__pycache__/channelList.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/channelPartEvent.cpython-35.pyc
Normal file
BIN
__pycache__/channelPartEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/clientPackets.cpython-35.pyc
Normal file
BIN
__pycache__/clientPackets.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/config.cpython-35.pyc
Normal file
BIN
__pycache__/config.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/consoleHelper.cpython-35.pyc
Normal file
BIN
__pycache__/consoleHelper.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/countryHelper.cpython-35.pyc
Normal file
BIN
__pycache__/countryHelper.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/createMatchEvent.cpython-35.pyc
Normal file
BIN
__pycache__/createMatchEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/crypt.cpython-35.pyc
Normal file
BIN
__pycache__/crypt.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/dataTypes.cpython-35.pyc
Normal file
BIN
__pycache__/dataTypes.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/databaseHelper.cpython-35.pyc
Normal file
BIN
__pycache__/databaseHelper.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/exceptions.cpython-35.pyc
Normal file
BIN
__pycache__/exceptions.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/fokabot.cpython-35.pyc
Normal file
BIN
__pycache__/fokabot.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/fokabotCommands.cpython-35.pyc
Normal file
BIN
__pycache__/fokabotCommands.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/friendAddEvent.cpython-35.pyc
Normal file
BIN
__pycache__/friendAddEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/friendRemoveEvent.cpython-35.pyc
Normal file
BIN
__pycache__/friendRemoveEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/gameModes.cpython-35.pyc
Normal file
BIN
__pycache__/gameModes.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/generalFunctions.cpython-35.pyc
Normal file
BIN
__pycache__/generalFunctions.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/glob.cpython-35.pyc
Normal file
BIN
__pycache__/glob.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/joinLobbyEvent.cpython-35.pyc
Normal file
BIN
__pycache__/joinLobbyEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/joinMatchEvent.cpython-35.pyc
Normal file
BIN
__pycache__/joinMatchEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/locationHelper.cpython-35.pyc
Normal file
BIN
__pycache__/locationHelper.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/loginEvent.cpython-35.pyc
Normal file
BIN
__pycache__/loginEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/logoutEvent.cpython-35.pyc
Normal file
BIN
__pycache__/logoutEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/match.cpython-35.pyc
Normal file
BIN
__pycache__/match.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/matchBeatmapEvent.cpython-35.pyc
Normal file
BIN
__pycache__/matchBeatmapEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/matchChangeTeamEvent.cpython-35.pyc
Normal file
BIN
__pycache__/matchChangeTeamEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/matchCompleteEvent.cpython-35.pyc
Normal file
BIN
__pycache__/matchCompleteEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/matchFailedEvent.cpython-35.pyc
Normal file
BIN
__pycache__/matchFailedEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/matchFramesEvent.cpython-35.pyc
Normal file
BIN
__pycache__/matchFramesEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/matchHasBeatmapEvent.cpython-35.pyc
Normal file
BIN
__pycache__/matchHasBeatmapEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/matchInviteEvent.cpython-35.pyc
Normal file
BIN
__pycache__/matchInviteEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/matchList.cpython-35.pyc
Normal file
BIN
__pycache__/matchList.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/matchLockEvent.cpython-35.pyc
Normal file
BIN
__pycache__/matchLockEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/matchModModes.cpython-35.pyc
Normal file
BIN
__pycache__/matchModModes.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/matchNoBeatmapEvent.cpython-35.pyc
Normal file
BIN
__pycache__/matchNoBeatmapEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/matchPlayerLoadEvent.cpython-35.pyc
Normal file
BIN
__pycache__/matchPlayerLoadEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/matchReadyEvent.cpython-35.pyc
Normal file
BIN
__pycache__/matchReadyEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/matchScoringTypes.cpython-35.pyc
Normal file
BIN
__pycache__/matchScoringTypes.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/matchSkipEvent.cpython-35.pyc
Normal file
BIN
__pycache__/matchSkipEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/matchStartEvent.cpython-35.pyc
Normal file
BIN
__pycache__/matchStartEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/matchTeamTypes.cpython-35.pyc
Normal file
BIN
__pycache__/matchTeamTypes.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/matchTeams.cpython-35.pyc
Normal file
BIN
__pycache__/matchTeams.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/matchTransferHostEvent.cpython-35.pyc
Normal file
BIN
__pycache__/matchTransferHostEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/mods.cpython-35.pyc
Normal file
BIN
__pycache__/mods.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/osuToken.cpython-35.pyc
Normal file
BIN
__pycache__/osuToken.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/packetHelper.cpython-35.pyc
Normal file
BIN
__pycache__/packetHelper.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/packetIDs.cpython-35.pyc
Normal file
BIN
__pycache__/packetIDs.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/partLobbyEvent.cpython-35.pyc
Normal file
BIN
__pycache__/partLobbyEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/partMatchEvent.cpython-35.pyc
Normal file
BIN
__pycache__/partMatchEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/passwordHelper.cpython-35.pyc
Normal file
BIN
__pycache__/passwordHelper.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/responseHelper.cpython-35.pyc
Normal file
BIN
__pycache__/responseHelper.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/sendPrivateMessageEvent.cpython-35.pyc
Normal file
BIN
__pycache__/sendPrivateMessageEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/sendPublicMessageEvent.cpython-35.pyc
Normal file
BIN
__pycache__/sendPublicMessageEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/serverPackets.cpython-35.pyc
Normal file
BIN
__pycache__/serverPackets.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/setAwayMessageEvent.cpython-35.pyc
Normal file
BIN
__pycache__/setAwayMessageEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/slotStatuses.cpython-35.pyc
Normal file
BIN
__pycache__/slotStatuses.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/spectateFramesEvent.cpython-35.pyc
Normal file
BIN
__pycache__/spectateFramesEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/startSpectatingEvent.cpython-35.pyc
Normal file
BIN
__pycache__/startSpectatingEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/stopSpectatingEvent.cpython-35.pyc
Normal file
BIN
__pycache__/stopSpectatingEvent.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/systemHelper.cpython-35.pyc
Normal file
BIN
__pycache__/systemHelper.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/tokenList.cpython-35.pyc
Normal file
BIN
__pycache__/tokenList.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/userHelper.cpython-35.pyc
Normal file
BIN
__pycache__/userHelper.cpython-35.pyc
Normal file
Binary file not shown.
BIN
__pycache__/userRanks.cpython-35.pyc
Normal file
BIN
__pycache__/userRanks.cpython-35.pyc
Normal file
Binary file not shown.
17
actions.py
Normal file
17
actions.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
"""Contains user actions"""
|
||||
#TODO: Uppercase
|
||||
idle = 0
|
||||
afk = 1
|
||||
playing = 2
|
||||
editing = 3
|
||||
modding = 4
|
||||
multiplayer = 5
|
||||
watching = 6
|
||||
unknown = 7
|
||||
testing = 8
|
||||
submitting = 9
|
||||
paused = 10
|
||||
lobby = 11
|
||||
multiplaying= 12
|
||||
osuDirect = 13
|
||||
none = 14
|
42
banchoConfig.py
Normal file
42
banchoConfig.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
import glob
|
||||
import generalFunctions
|
||||
|
||||
class banchoConfig:
|
||||
"""
|
||||
Class that loads settings from bancho_settings db table
|
||||
"""
|
||||
|
||||
config = {"banchoMaintenance": False, "freeDirect": True, "menuIcon": "", "loginNotification": ""}
|
||||
|
||||
def __init__(self, __loadFromDB = True):
|
||||
"""
|
||||
Initialize a banchoConfig object (and load bancho_settings from db)
|
||||
|
||||
[__loadFromDB -- if True, load values from db. If False, don't load values. Default: True]
|
||||
"""
|
||||
|
||||
if __loadFromDB:
|
||||
try:
|
||||
self.loadSettings()
|
||||
except:
|
||||
raise
|
||||
|
||||
def loadSettings(self):
|
||||
"""
|
||||
(re)load bancho_settings from DB and set values in config array
|
||||
"""
|
||||
|
||||
self.config["banchoMaintenance"] = generalFunctions.stringToBool(glob.db.fetch("SELECT value_int FROM bancho_settings WHERE name = 'bancho_maintenance'")["value_int"])
|
||||
self.config["freeDirect"] = generalFunctions.stringToBool(glob.db.fetch("SELECT value_int FROM bancho_settings WHERE name = 'free_direct'")["value_int"])
|
||||
self.config["menuIcon"] = glob.db.fetch("SELECT value_string FROM bancho_settings WHERE name = 'menu_icon'")["value_string"]
|
||||
self.config["loginNotification"] = glob.db.fetch("SELECT value_string FROM bancho_settings WHERE name = 'login_notification'")["value_string"]
|
||||
|
||||
def setMaintenance(self, __maintenance):
|
||||
"""
|
||||
Turn on/off bancho maintenance mode. Write new value to db too
|
||||
|
||||
__maintenance -- if True, turn on maintenance mode. If false, turn it off
|
||||
"""
|
||||
|
||||
self.config["banchoMaintenance"] = __maintenance
|
||||
glob.db.execute("UPDATE bancho_settings SET value_int = ? WHERE name = 'bancho_maintenance'", [int(__maintenance)])
|
9
bcolors.py
Normal file
9
bcolors.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
"""Console colors"""
|
||||
PINK = '\033[95m'
|
||||
BLUE = '\033[94m'
|
||||
GREEN = '\033[92m'
|
||||
YELLOW = '\033[93m'
|
||||
RED = '\033[91m'
|
||||
ENDC = '\033[0m'
|
||||
BOLD = '\033[1m'
|
||||
UNDERLINE = '\033[4m'
|
21
cantSpectateEvent.py
Normal file
21
cantSpectateEvent.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
import glob
|
||||
import serverPackets
|
||||
import consoleHelper
|
||||
import bcolors
|
||||
import exceptions
|
||||
|
||||
def handle(userToken, packetData):
|
||||
# get usertoken data
|
||||
userID = userToken.userID
|
||||
|
||||
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()
|
26
changeActionEvent.py
Normal file
26
changeActionEvent.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
import glob
|
||||
import clientPackets
|
||||
import serverPackets
|
||||
import actions
|
||||
|
||||
def handle(userToken, packetData):
|
||||
# Get usertoken data
|
||||
userID = userToken.userID
|
||||
username = userToken.username
|
||||
|
||||
# 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("> {} changed action: {} [{}][{}]".format(username, str(userToken.actionID), userToken.actionText, userToken.actionMd5))
|
43
changeMatchModsEvent.py
Normal file
43
changeMatchModsEvent.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
import glob
|
||||
import clientPackets
|
||||
import matchModModes
|
||||
import mods
|
||||
|
||||
def handle(userToken, packetData):
|
||||
# Get token data
|
||||
userID = userToken.userID
|
||||
|
||||
# Get packet data
|
||||
packetData = clientPackets.changeMods(packetData)
|
||||
|
||||
# Make sure the match exists
|
||||
matchID = userToken.matchID
|
||||
if matchID not in glob.matches.matches:
|
||||
return
|
||||
match = glob.matches.matches[matchID]
|
||||
|
||||
# Set slot or match mods according to modType
|
||||
if match.matchModMode == matchModModes.freeMod:
|
||||
# Freemod
|
||||
|
||||
# Host can set global DT/HT
|
||||
if userID == match.hostUserID:
|
||||
# If host has selected DT/HT and Freemod is enabled, set DT/HT as match mod
|
||||
if (packetData["mods"] & mods.DoubleTime) > 0:
|
||||
match.changeMatchMods(mods.DoubleTime)
|
||||
# Nighcore
|
||||
if (packetData["mods"] & mods.Nightcore) > 0:
|
||||
match.changeMatchMods(match.mods+mods.Nightcore)
|
||||
elif (packetData["mods"] & mods.HalfTime) > 0:
|
||||
match.changeMatchMods(mods.HalfTime)
|
||||
else:
|
||||
# No DT/HT, set global mods to 0 (we are in freemod mode)
|
||||
match.changeMatchMods(0)
|
||||
|
||||
# Set slot mods
|
||||
slotID = match.getUserSlotID(userID)
|
||||
if slotID != None:
|
||||
match.setSlotMods(slotID, packetData["mods"])
|
||||
else:
|
||||
# Not freemod, set match mods
|
||||
match.changeMatchMods(packetData["mods"])
|
17
changeMatchPasswordEvent.py
Normal file
17
changeMatchPasswordEvent.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
import clientPackets
|
||||
import glob
|
||||
|
||||
def handle(userToken, packetData):
|
||||
# Read packet data. Same structure as changeMatchSettings
|
||||
packetData = clientPackets.changeMatchSettings(packetData)
|
||||
|
||||
# Make sure the match exists
|
||||
matchID = userToken.matchID
|
||||
if matchID not in glob.matches.matches:
|
||||
return
|
||||
|
||||
# Get our match
|
||||
match = glob.matches.matches[matchID]
|
||||
|
||||
# Update match password
|
||||
match.changePassword(packetData["matchPassword"])
|
109
changeMatchSettingsEvent.py
Normal file
109
changeMatchSettingsEvent.py
Normal file
|
@ -0,0 +1,109 @@
|
|||
import glob
|
||||
import clientPackets
|
||||
import matchModModes
|
||||
import consoleHelper
|
||||
import bcolors
|
||||
import random
|
||||
import matchTeamTypes
|
||||
import matchTeams
|
||||
import slotStatuses
|
||||
|
||||
def handle(userToken, packetData):
|
||||
# Read new settings
|
||||
packetData = clientPackets.changeMatchSettings(packetData)
|
||||
|
||||
# Get match ID
|
||||
matchID = userToken.matchID
|
||||
|
||||
# Make sure the match exists
|
||||
if matchID not in glob.matches.matches:
|
||||
return
|
||||
|
||||
# Get match object
|
||||
match = glob.matches.matches[matchID]
|
||||
|
||||
# Some dank memes easter egg
|
||||
memeTitles = [
|
||||
"RWC 2020",
|
||||
"Fokabot is a duck",
|
||||
"Dank memes",
|
||||
"1337ms Ping",
|
||||
"Iscriviti a Xenotoze",
|
||||
"...e i marò?",
|
||||
"Superman dies",
|
||||
"The brace is on fire",
|
||||
"print_foot()",
|
||||
"#FREEZEBARKEZ",
|
||||
"Ripple devs are actually cats",
|
||||
"Thank Mr Shaural",
|
||||
"NEVER GIVE UP",
|
||||
"T I E D W I T H U N I T E D",
|
||||
"HIGHEST HDHR LOBBY OF ALL TIME",
|
||||
"This is gasoline and I set myself on fire",
|
||||
"Everyone is cheating apparently",
|
||||
"Kurwa mac",
|
||||
"TATOE",
|
||||
"This is not your drama landfill.",
|
||||
"I like cheese",
|
||||
"NYO IS NOT A CAT HE IS A DO(N)G",
|
||||
"Datingu startuato"
|
||||
]
|
||||
|
||||
# Set match name
|
||||
match.matchName = packetData["matchName"] if packetData["matchName"] != "meme" else random.choice(memeTitles)
|
||||
|
||||
# Update match settings
|
||||
match.inProgress = packetData["inProgress"]
|
||||
match.matchPassword = packetData["matchPassword"]
|
||||
match.beatmapName = packetData["beatmapName"]
|
||||
match.beatmapID = packetData["beatmapID"]
|
||||
match.hostUserID = packetData["hostUserID"]
|
||||
match.gameMode = packetData["gameMode"]
|
||||
|
||||
oldBeatmapMD5 = match.beatmapMD5
|
||||
oldMods = match.mods
|
||||
|
||||
match.mods = packetData["mods"]
|
||||
match.beatmapMD5 = packetData["beatmapMD5"]
|
||||
match.matchScoringType = packetData["scoringType"]
|
||||
match.matchTeamType = packetData["teamType"]
|
||||
match.matchModMode = packetData["freeMods"]
|
||||
|
||||
# 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
|
||||
|
||||
# Reset mods if needed
|
||||
if match.matchModMode == matchModModes.normal:
|
||||
# Reset slot mods if not freeMods
|
||||
for i in range(0,16):
|
||||
match.slots[i]["mods"] = 0
|
||||
else:
|
||||
# Reset match mods if freemod
|
||||
match.mods = 0
|
||||
|
||||
# Set/reset teams
|
||||
if match.matchTeamType == matchTeamTypes.teamVs or match.matchTeamType == matchTeamTypes.tagTeamVs:
|
||||
# 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
|
||||
c+=1
|
||||
else:
|
||||
# Reset teams
|
||||
for i in range(0,16):
|
||||
match.slots[i]["team"] = matchTeams.noTeam
|
||||
|
||||
# Force no freemods if tag coop
|
||||
if match.matchTeamType == matchTeamTypes.tagCoop or match.matchTeamType == matchTeamTypes.tagTeamVs:
|
||||
match.matchModMode = matchModModes.normal
|
||||
|
||||
# Send updated settings
|
||||
match.sendUpdate()
|
||||
|
||||
# Console output
|
||||
consoleHelper.printColored("> MPROOM{}: Updated room settings".format(match.matchID), bcolors.BLUE)
|
||||
#consoleHelper.printColored("> MPROOM{}: DEBUG: Host is {}".format(match.matchID, match.hostUserID), bcolors.PINK)
|
18
changeSlotEvent.py
Normal file
18
changeSlotEvent.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
import clientPackets
|
||||
import glob
|
||||
import consoleHelper
|
||||
import bcolors
|
||||
|
||||
def handle(userToken, packetData):
|
||||
# Get usertoken data
|
||||
userID = userToken.userID
|
||||
username = userToken.username
|
||||
|
||||
# Read packet data
|
||||
packetData = clientPackets.changeSlot(packetData)
|
||||
|
||||
# Get match
|
||||
match = glob.matches.matches[userToken.matchID]
|
||||
|
||||
# Change slot
|
||||
match.userChangeSlot(userID, packetData["slotID"])
|
78
channel.py
Normal file
78
channel.py
Normal file
|
@ -0,0 +1,78 @@
|
|||
class channel:
|
||||
"""
|
||||
A chat channel
|
||||
|
||||
name -- channel name
|
||||
description -- channel description
|
||||
connectedUsers -- connected users IDs list
|
||||
publicRead -- bool
|
||||
publicWrite -- bool
|
||||
moderated -- bool
|
||||
"""
|
||||
|
||||
name = ""
|
||||
description = ""
|
||||
connectedUsers = []
|
||||
|
||||
publicRead = False
|
||||
publicWrite = False
|
||||
moderated = False
|
||||
|
||||
def __init__(self, __name, __description, __publicRead, __publicWrite):
|
||||
"""
|
||||
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
|
||||
"""
|
||||
|
||||
self.name = __name
|
||||
self.description = __description
|
||||
self.publicRead = __publicRead
|
||||
self.publicWrite = __publicWrite
|
||||
self.connectedUsers = []
|
||||
|
||||
|
||||
def userJoin(self, __userID):
|
||||
"""
|
||||
Add a user to connected users
|
||||
|
||||
__userID -- user ID that joined the channel
|
||||
"""
|
||||
|
||||
if __userID not in self.connectedUsers:
|
||||
self.connectedUsers.append(__userID)
|
||||
|
||||
|
||||
def userPart(self, __userID):
|
||||
"""
|
||||
Remove a user from connected users
|
||||
|
||||
__userID -- user ID that left the channel
|
||||
"""
|
||||
|
||||
connectedUsers = self.connectedUsers
|
||||
if __userID in connectedUsers:
|
||||
connectedUsers.remove(__userID)
|
||||
|
||||
|
||||
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)
|
56
channelJoinEvent.py
Normal file
56
channelJoinEvent.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
"""
|
||||
Event called when someone joins a channel
|
||||
"""
|
||||
|
||||
import clientPackets
|
||||
import consoleHelper
|
||||
import bcolors
|
||||
import serverPackets
|
||||
import glob
|
||||
import exceptions
|
||||
|
||||
def handle(userToken, packetData):
|
||||
# Channel join packet
|
||||
packetData = clientPackets.channelJoin(packetData)
|
||||
joinChannel(userToken, packetData["channel"])
|
||||
|
||||
def joinChannel(userToken, channelName):
|
||||
'''
|
||||
Join a channel
|
||||
|
||||
userToken -- user token object of user that joins the chanlle
|
||||
channelName -- name of channel
|
||||
'''
|
||||
try:
|
||||
# Get usertoken data
|
||||
username = userToken.username
|
||||
userID = userToken.userID
|
||||
userRank = userToken.rank
|
||||
|
||||
# Check spectator channel
|
||||
# If it's spectator channel, skip checks and list stuff
|
||||
if channelName != "#spectator" and channelName != "#multiplayer":
|
||||
# Normal channel, do check stuff
|
||||
# Make sure the channel exists
|
||||
if channelName not in glob.channels.channels:
|
||||
raise exceptions.channelUnknownException
|
||||
|
||||
# Check channel permissions
|
||||
if glob.channels.channels[channelName].publicRead == False and userRank <= 2:
|
||||
raise exceptions.channelNoPermissionsException
|
||||
|
||||
# Add our userID to users in that channel
|
||||
glob.channels.channels[channelName].userJoin(userID)
|
||||
|
||||
# Add the channel to our joined channel
|
||||
userToken.joinChannel(channelName)
|
||||
|
||||
# Send channel joined
|
||||
userToken.enqueue(serverPackets.channelJoinSuccess(userID, channelName))
|
||||
|
||||
# Console output
|
||||
consoleHelper.printColored("> {} joined channel {}".format(username, channelName), bcolors.GREEN)
|
||||
except exceptions.channelNoPermissionsException:
|
||||
consoleHelper.printColored("[!] {} attempted to join channel {}, but they have no read permissions".format(username, channelName), bcolors.RED)
|
||||
except exceptions.channelUnknownException:
|
||||
consoleHelper.printColored("[!] {} attempted to join an unknown channel ({})".format(username, channelName), bcolors.RED)
|
40
channelList.py
Normal file
40
channelList.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
import glob
|
||||
import channel
|
||||
|
||||
class channelList:
|
||||
"""
|
||||
Channel list
|
||||
|
||||
channels -- dictionary. key: channel name, value: channel object
|
||||
"""
|
||||
|
||||
channels = {}
|
||||
|
||||
|
||||
def loadChannels(self):
|
||||
"""
|
||||
Load chat channels from db and add them to channels dictionary
|
||||
"""
|
||||
|
||||
# Get channels from DB
|
||||
channels = glob.db.fetchAll("SELECT * FROM bancho_channels")
|
||||
|
||||
# Add each channel if needed
|
||||
for i in channels:
|
||||
if i["name"] not in self.channels:
|
||||
publicRead = True if i["public_read"] == 1 else False
|
||||
publicWrite = True if i["public_write"] == 1 else False
|
||||
self.addChannel(i["name"], i["description"], publicRead, publicWrite)
|
||||
|
||||
|
||||
def addChannel(self, __name, __description, __publicRead, __publicWrite):
|
||||
"""
|
||||
Add a channel object to channels dictionary
|
||||
|
||||
__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
|
||||
"""
|
||||
|
||||
self.channels[__name] = channel.channel(__name, __description, __publicRead, __publicWrite)
|
36
channelPartEvent.py
Normal file
36
channelPartEvent.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
"""
|
||||
Event called when someone parts a channel
|
||||
"""
|
||||
|
||||
import consoleHelper
|
||||
import bcolors
|
||||
import glob
|
||||
import clientPackets
|
||||
import serverPackets
|
||||
|
||||
def handle(userToken, packetData):
|
||||
# Channel part packet
|
||||
packetData = clientPackets.channelPart(packetData)
|
||||
partChannel(userToken, packetData["channel"])
|
||||
|
||||
def partChannel(userToken, channelName, kick = False):
|
||||
# Get usertoken data
|
||||
username = userToken.username
|
||||
userID = userToken.userID
|
||||
|
||||
# Remove us from joined users and joined channels
|
||||
if channelName in glob.channels.channels:
|
||||
# Check that user is in channel
|
||||
if channelName in userToken.joinedChannels:
|
||||
userToken.partChannel(channelName)
|
||||
|
||||
# Check if user is in channel
|
||||
if userID in glob.channels.channels[channelName].connectedUsers:
|
||||
glob.channels.channels[channelName].userPart(userID)
|
||||
|
||||
# Force close tab if needed
|
||||
if kick == True:
|
||||
userToken.enqueue(serverPackets.channelKicked(channelName))
|
||||
|
||||
# Console output
|
||||
consoleHelper.printColored("> {} parted channel {}".format(username, channelName), bcolors.YELLOW)
|
143
clientPackets.py
Normal file
143
clientPackets.py
Normal file
|
@ -0,0 +1,143 @@
|
|||
""" Contains functions used to read specific client packets from byte stream """
|
||||
import dataTypes
|
||||
import packetHelper
|
||||
import slotStatuses
|
||||
|
||||
|
||||
""" General packets """
|
||||
def userActionChange(stream):
|
||||
return packetHelper.readPacketData(stream,
|
||||
[
|
||||
["actionID", dataTypes.byte],
|
||||
["actionText", dataTypes.string],
|
||||
["actionMd5", dataTypes.string],
|
||||
["actionMods", dataTypes.uInt32],
|
||||
["gameMode", dataTypes.byte]
|
||||
])
|
||||
|
||||
|
||||
|
||||
""" Client chat packets """
|
||||
def sendPublicMessage(stream):
|
||||
return packetHelper.readPacketData(stream,
|
||||
[
|
||||
["unknown", dataTypes.string],
|
||||
["message", dataTypes.string],
|
||||
["to", dataTypes.string]
|
||||
])
|
||||
|
||||
def sendPrivateMessage(stream):
|
||||
return packetHelper.readPacketData(stream,
|
||||
[
|
||||
["unknown", dataTypes.string],
|
||||
["message", dataTypes.string],
|
||||
["to", dataTypes.string],
|
||||
["unknown2", dataTypes.uInt32]
|
||||
])
|
||||
|
||||
def setAwayMessage(stream):
|
||||
return packetHelper.readPacketData(stream,
|
||||
[
|
||||
["unknown", dataTypes.string],
|
||||
["awayMessage", dataTypes.string]
|
||||
])
|
||||
|
||||
def channelJoin(stream):
|
||||
return packetHelper.readPacketData(stream,[["channel", dataTypes.string]])
|
||||
|
||||
def channelPart(stream):
|
||||
return packetHelper.readPacketData(stream,[["channel", dataTypes.string]])
|
||||
|
||||
def addRemoveFriend(stream):
|
||||
return packetHelper.readPacketData(stream, [["friendID", dataTypes.sInt32]])
|
||||
|
||||
|
||||
|
||||
""" SPECTATOR PACKETS """
|
||||
def startSpectating(stream):
|
||||
return packetHelper.readPacketData(stream,[["userID", dataTypes.sInt32]])
|
||||
|
||||
|
||||
""" MULTIPLAYER PACKETS """
|
||||
def matchSettings(stream):
|
||||
# Data to return, will be merged later
|
||||
data = []
|
||||
|
||||
# Some settings
|
||||
struct = [
|
||||
["matchID", dataTypes.uInt16],
|
||||
["inProgress", dataTypes.byte],
|
||||
["unknown", dataTypes.byte],
|
||||
["mods", dataTypes.uInt32],
|
||||
["matchName", dataTypes.string],
|
||||
["matchPassword", dataTypes.string],
|
||||
["beatmapName", dataTypes.string],
|
||||
["beatmapID", dataTypes.uInt32],
|
||||
["beatmapMD5", dataTypes.string]
|
||||
]
|
||||
|
||||
# Slot statuses (not used)
|
||||
for i in range(0,16):
|
||||
struct.append(["slot{}Status".format(str(i)), dataTypes.byte])
|
||||
|
||||
# Slot statuses (not used)
|
||||
for i in range(0,16):
|
||||
struct.append(["slot{}Team".format(str(i)), dataTypes.byte])
|
||||
|
||||
# Read first part
|
||||
data.append(packetHelper.readPacketData(stream, struct))
|
||||
|
||||
# Skip userIDs because fuck
|
||||
start = 7+2+1+1+4+4+16+16+len(data[0]["matchName"])+len(data[0]["matchPassword"])+len(data[0]["beatmapMD5"])+len(data[0]["beatmapName"])
|
||||
start += 1 if (data[0]["matchName"] == "") else 2
|
||||
start += 1 if (data[0]["matchPassword"] == "") else 2
|
||||
start += 2 # If beatmap name and MD5 don't change, the client sends \x0b\x00 istead of \x00 only, so always add 2. ...WHY!
|
||||
start += 2
|
||||
for i in range(0,16):
|
||||
s = data[0]["slot{}Status".format(str(i))]
|
||||
if s != slotStatuses.free and s != slotStatuses.locked:
|
||||
start += 4
|
||||
|
||||
# Other settings
|
||||
struct = [
|
||||
["hostUserID", dataTypes.sInt32],
|
||||
["gameMode", dataTypes.byte],
|
||||
["scoringType", dataTypes.byte],
|
||||
["teamType", dataTypes.byte],
|
||||
["freeMods", dataTypes.byte],
|
||||
]
|
||||
|
||||
# Read last part
|
||||
data.append(packetHelper.readPacketData(stream[start:], struct, False))
|
||||
|
||||
# Mods if freemod (not used)
|
||||
#if data[1]["freeMods"] == 1:
|
||||
|
||||
result = {}
|
||||
for i in data:
|
||||
result.update(i)
|
||||
return result
|
||||
|
||||
def createMatch(stream):
|
||||
return matchSettings(stream)
|
||||
|
||||
def changeMatchSettings(stream):
|
||||
return matchSettings(stream)
|
||||
|
||||
def changeSlot(stream):
|
||||
return packetHelper.readPacketData(stream, [["slotID", dataTypes.uInt32]])
|
||||
|
||||
def joinMatch(stream):
|
||||
return packetHelper.readPacketData(stream, [["matchID", dataTypes.uInt32], ["password", dataTypes.string]])
|
||||
|
||||
def changeMods(stream):
|
||||
return packetHelper.readPacketData(stream, [["mods", dataTypes.uInt32]])
|
||||
|
||||
def lockSlot(stream):
|
||||
return packetHelper.readPacketData(stream, [["slotID", dataTypes.uInt32]])
|
||||
|
||||
def transferHost(stream):
|
||||
return packetHelper.readPacketData(stream, [["slotID", dataTypes.uInt32]])
|
||||
|
||||
def matchInvite(stream):
|
||||
return packetHelper.readPacketData(stream, [["userID", dataTypes.uInt32]])
|
24
config.ini
Normal file
24
config.ini
Normal file
|
@ -0,0 +1,24 @@
|
|||
[db]
|
||||
host = localhost
|
||||
username = root
|
||||
password = meme
|
||||
database = heidi
|
||||
pingtime = 600
|
||||
|
||||
[server]
|
||||
server = tornado
|
||||
host = 0.0.0.0
|
||||
port = 5001
|
||||
outputpackets = 0
|
||||
outputrequesttime = 0
|
||||
localizeusers = 0
|
||||
timeouttime = 100
|
||||
timeoutlooptime = 100
|
||||
|
||||
[flask]
|
||||
threaded = 1
|
||||
debug = 1
|
||||
logger = 0
|
||||
|
||||
[ci]
|
||||
key=rippleburgrw15gofmustard
|
107
config.py
Normal file
107
config.py
Normal file
|
@ -0,0 +1,107 @@
|
|||
import os
|
||||
import configparser
|
||||
|
||||
class config:
|
||||
"""
|
||||
config.ini object
|
||||
|
||||
config -- list with ini data
|
||||
default -- if true, we have generated a default config.ini
|
||||
"""
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
fileName = "" # config filename
|
||||
default = True
|
||||
|
||||
# Check if config.ini exists and load/generate it
|
||||
def __init__(self, __file):
|
||||
"""
|
||||
Initialize a config object
|
||||
|
||||
__file -- filename
|
||||
"""
|
||||
|
||||
self.fileName = __file
|
||||
if os.path.isfile(self.fileName):
|
||||
# config.ini found, load it
|
||||
self.config.read(self.fileName)
|
||||
self.default = False
|
||||
else:
|
||||
# config.ini not found, generate a default one
|
||||
self.generateDefaultConfig()
|
||||
self.default = True
|
||||
|
||||
|
||||
# Check if config.ini has all needed the keys
|
||||
def checkConfig(self):
|
||||
"""
|
||||
Check if this config has the required keys
|
||||
|
||||
return -- True if valid, False if not
|
||||
"""
|
||||
|
||||
try:
|
||||
# Try to get all the required keys
|
||||
self.config.get("db","host")
|
||||
self.config.get("db","username")
|
||||
self.config.get("db","password")
|
||||
self.config.get("db","database")
|
||||
self.config.get("db","pingtime")
|
||||
|
||||
self.config.get("server","server")
|
||||
self.config.get("server","host")
|
||||
self.config.get("server","port")
|
||||
self.config.get("server","localizeusers")
|
||||
self.config.get("server","outputpackets")
|
||||
self.config.get("server","outputrequesttime")
|
||||
self.config.get("server","timeouttime")
|
||||
self.config.get("server","timeoutlooptime")
|
||||
|
||||
if self.config["server"]["server"] == "flask":
|
||||
# Flask only config
|
||||
self.config.get("flask","threaded")
|
||||
self.config.get("flask","debug")
|
||||
self.config.get("flask","logger")
|
||||
|
||||
self.config.get("ci","key")
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
# Generate a default config.ini
|
||||
def generateDefaultConfig(self):
|
||||
"""Open and set default keys for that config file"""
|
||||
|
||||
# Open config.ini in write mode
|
||||
f = open(self.fileName, "w")
|
||||
|
||||
# Set keys to config object
|
||||
self.config.add_section("db")
|
||||
self.config.set("db", "host", "localhost")
|
||||
self.config.set("db", "username", "root")
|
||||
self.config.set("db", "password", "")
|
||||
self.config.set("db", "database", "ripple")
|
||||
self.config.set("db", "pingtime", "600")
|
||||
|
||||
self.config.add_section("server")
|
||||
self.config.set("server", "server", "tornado")
|
||||
self.config.set("server", "host", "0.0.0.0")
|
||||
self.config.set("server", "port", "5001")
|
||||
self.config.set("server", "localizeusers", "1")
|
||||
self.config.set("server", "outputpackets", "0")
|
||||
self.config.set("server", "outputrequesttime", "0")
|
||||
self.config.set("server", "timeoutlooptime", "100")
|
||||
self.config.set("server", "timeouttime", "100")
|
||||
|
||||
self.config.add_section("flask")
|
||||
self.config.set("flask", "threaded", "1")
|
||||
self.config.set("flask", "debug", "0")
|
||||
self.config.set("flask", "logger", "0")
|
||||
|
||||
self.config.add_section("ci")
|
||||
self.config.set("ci", "key", "changeme")
|
||||
|
||||
# Write ini to file and close
|
||||
self.config.write(f)
|
||||
f.close()
|
71
consoleHelper.py
Normal file
71
consoleHelper.py
Normal file
|
@ -0,0 +1,71 @@
|
|||
"""Some console related functions"""
|
||||
|
||||
import bcolors
|
||||
import glob
|
||||
|
||||
def printServerStartHeader(asciiArt):
|
||||
"""Print server start header with optional ascii art
|
||||
|
||||
asciiArt -- if True, will print ascii art too"""
|
||||
|
||||
if asciiArt == True:
|
||||
print("{} _ __".format(bcolors.GREEN))
|
||||
print(" (_) / /")
|
||||
print(" ______ __ ____ ____ / /____")
|
||||
print(" / ___/ / _ \\/ _ \\/ / _ \\")
|
||||
print(" / / / / /_) / /_) / / ____/")
|
||||
print("/__/ /__/ .___/ .___/__/ \\_____/")
|
||||
print(" / / / /")
|
||||
print(" /__/ /__/\r\n")
|
||||
print(" .. o .")
|
||||
print(" o.o o . o")
|
||||
print(" oo...")
|
||||
print(" __[]__")
|
||||
print(" nyo --> _\\:D/_/o_o_o_|__ u wot m8")
|
||||
print(" \\\"\"\"\"\"\"\"\"\"\"\"\"\"\"/")
|
||||
print(" \\ . .. .. . /")
|
||||
print("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^{}".format(bcolors.ENDC))
|
||||
|
||||
printColored("> Welcome to pep.py osu!bancho server v{}".format(glob.VERSION), bcolors.GREEN)
|
||||
printColored("> Made by the Ripple team", bcolors.GREEN)
|
||||
printColored("> {}https://github.com/osuripple/ripple".format(bcolors.UNDERLINE), bcolors.GREEN)
|
||||
printColored("> Press CTRL+C to exit\n",bcolors.GREEN)
|
||||
|
||||
|
||||
def printNoNl(string):
|
||||
"""
|
||||
Print string without new line at the end
|
||||
|
||||
string -- string to print
|
||||
"""
|
||||
|
||||
print(string, end="")
|
||||
|
||||
|
||||
def printColored(string, color):
|
||||
"""
|
||||
Print colored string
|
||||
|
||||
string -- string to print
|
||||
color -- see bcolors.py
|
||||
"""
|
||||
|
||||
print("{}{}{}".format(color, string, bcolors.ENDC))
|
||||
|
||||
|
||||
def printError():
|
||||
"""Print error text FOR LOADING"""
|
||||
|
||||
printColored("Error", bcolors.RED)
|
||||
|
||||
|
||||
def printDone():
|
||||
"""Print error text FOR LOADING"""
|
||||
|
||||
printColored("Done", bcolors.GREEN)
|
||||
|
||||
|
||||
def printWarning():
|
||||
"""Print error text FOR LOADING"""
|
||||
|
||||
printColored("Warning", bcolors.YELLOW)
|
282
countryHelper.py
Normal file
282
countryHelper.py
Normal file
|
@ -0,0 +1,282 @@
|
|||
"""Contains all country codes with their osu numeric code"""
|
||||
|
||||
countryCodes = {
|
||||
"LV": 132,
|
||||
"AD": 3,
|
||||
"LT": 130,
|
||||
"KM": 116,
|
||||
"QA": 182,
|
||||
"VA": 0,
|
||||
"PK": 173,
|
||||
"KI": 115,
|
||||
"SS": 0,
|
||||
"KH": 114,
|
||||
"NZ": 166,
|
||||
"TO": 215,
|
||||
"KZ": 122,
|
||||
"GA": 76,
|
||||
"BW": 35,
|
||||
"AX": 247,
|
||||
"GE": 79,
|
||||
"UA": 222,
|
||||
"CR": 50,
|
||||
"AE": 0,
|
||||
"NE": 157,
|
||||
"ZA": 240,
|
||||
"SK": 196,
|
||||
"BV": 34,
|
||||
"SH": 0,
|
||||
"PT": 179,
|
||||
"SC": 189,
|
||||
"CO": 49,
|
||||
"GP": 86,
|
||||
"GY": 93,
|
||||
"CM": 47,
|
||||
"TJ": 211,
|
||||
"AF": 5,
|
||||
"IE": 101,
|
||||
"AL": 8,
|
||||
"BG": 24,
|
||||
"JO": 110,
|
||||
"MU": 149,
|
||||
"PM": 0,
|
||||
"LA": 0,
|
||||
"IO": 104,
|
||||
"KY": 121,
|
||||
"SA": 187,
|
||||
"KN": 0,
|
||||
"OM": 167,
|
||||
"CY": 54,
|
||||
"BQ": 0,
|
||||
"BT": 33,
|
||||
"WS": 236,
|
||||
"ES": 67,
|
||||
"LR": 128,
|
||||
"RW": 186,
|
||||
"AQ": 12,
|
||||
"PW": 180,
|
||||
"JE": 250,
|
||||
"TN": 214,
|
||||
"ZW": 243,
|
||||
"JP": 111,
|
||||
"BB": 20,
|
||||
"VN": 233,
|
||||
"HN": 96,
|
||||
"KP": 0,
|
||||
"WF": 235,
|
||||
"EC": 62,
|
||||
"HU": 99,
|
||||
"GF": 80,
|
||||
"GQ": 87,
|
||||
"TW": 220,
|
||||
"MC": 135,
|
||||
"BE": 22,
|
||||
"PN": 176,
|
||||
"SZ": 205,
|
||||
"CZ": 55,
|
||||
"LY": 0,
|
||||
"IN": 103,
|
||||
"FM": 0,
|
||||
"PY": 181,
|
||||
"PH": 172,
|
||||
"MN": 142,
|
||||
"GG": 248,
|
||||
"CC": 39,
|
||||
"ME": 242,
|
||||
"DO": 60,
|
||||
"KR": 0,
|
||||
"PL": 174,
|
||||
"MT": 148,
|
||||
"MM": 141,
|
||||
"AW": 17,
|
||||
"MV": 150,
|
||||
"BD": 21,
|
||||
"NR": 164,
|
||||
"AT": 15,
|
||||
"GW": 92,
|
||||
"FR": 74,
|
||||
"LI": 126,
|
||||
"CF": 41,
|
||||
"DZ": 61,
|
||||
"MA": 134,
|
||||
"VG": 0,
|
||||
"NC": 156,
|
||||
"IQ": 105,
|
||||
"BN": 0,
|
||||
"BF": 23,
|
||||
"BO": 30,
|
||||
"GB": 77,
|
||||
"CU": 51,
|
||||
"LU": 131,
|
||||
"YT": 238,
|
||||
"NO": 162,
|
||||
"SM": 198,
|
||||
"GL": 83,
|
||||
"IS": 107,
|
||||
"AO": 11,
|
||||
"MH": 138,
|
||||
"SE": 191,
|
||||
"ZM": 241,
|
||||
"FJ": 70,
|
||||
"SL": 197,
|
||||
"CH": 43,
|
||||
"RU": 0,
|
||||
"CW": 0,
|
||||
"CX": 53,
|
||||
"TF": 208,
|
||||
"NL": 161,
|
||||
"AU": 16,
|
||||
"FI": 69,
|
||||
"MS": 147,
|
||||
"GH": 81,
|
||||
"BY": 36,
|
||||
"IL": 102,
|
||||
"VC": 0,
|
||||
"NG": 159,
|
||||
"HT": 98,
|
||||
"LS": 129,
|
||||
"MR": 146,
|
||||
"YE": 237,
|
||||
"MP": 144,
|
||||
"SX": 0,
|
||||
"RE": 183,
|
||||
"RO": 184,
|
||||
"NP": 163,
|
||||
"CG": 0,
|
||||
"FO": 73,
|
||||
"CI": 0,
|
||||
"TH": 210,
|
||||
"HK": 94,
|
||||
"TK": 212,
|
||||
"XK": 0,
|
||||
"DM": 59,
|
||||
"LC": 0,
|
||||
"ID": 100,
|
||||
"MG": 137,
|
||||
"JM": 109,
|
||||
"IT": 108,
|
||||
"CA": 38,
|
||||
"TZ": 221,
|
||||
"GI": 82,
|
||||
"KG": 113,
|
||||
"NU": 165,
|
||||
"TV": 219,
|
||||
"LB": 124,
|
||||
"SY": 0,
|
||||
"PR": 177,
|
||||
"NI": 160,
|
||||
"KE": 112,
|
||||
"MO": 0,
|
||||
"SR": 201,
|
||||
"VI": 0,
|
||||
"SV": 203,
|
||||
"HM": 0,
|
||||
"CD": 0,
|
||||
"BI": 26,
|
||||
"BM": 28,
|
||||
"MW": 151,
|
||||
"TM": 213,
|
||||
"GT": 90,
|
||||
"AG": 0,
|
||||
"UM": 0,
|
||||
"US": 225,
|
||||
"AR": 13,
|
||||
"DJ": 57,
|
||||
"KW": 120,
|
||||
"MY": 153,
|
||||
"FK": 71,
|
||||
"EG": 64,
|
||||
"BA": 0,
|
||||
"CN": 48,
|
||||
"GN": 85,
|
||||
"PS": 178,
|
||||
"SO": 200,
|
||||
"IM": 249,
|
||||
"GS": 0,
|
||||
"BR": 31,
|
||||
"GM": 84,
|
||||
"PF": 170,
|
||||
"PA": 168,
|
||||
"PG": 171,
|
||||
"BH": 25,
|
||||
"TG": 209,
|
||||
"GU": 91,
|
||||
"CK": 45,
|
||||
"MF": 252,
|
||||
"VE": 230,
|
||||
"CL": 46,
|
||||
"TR": 217,
|
||||
"UG": 223,
|
||||
"GD": 78,
|
||||
"TT": 218,
|
||||
"TL": 0,
|
||||
"MD": 0,
|
||||
"MK": 0,
|
||||
"ST": 202,
|
||||
"CV": 52,
|
||||
"MQ": 145,
|
||||
"GR": 88,
|
||||
"HR": 97,
|
||||
"BZ": 37,
|
||||
"UZ": 227,
|
||||
"DK": 58,
|
||||
"SN": 199,
|
||||
"ET": 68,
|
||||
"VU": 234,
|
||||
"ER": 66,
|
||||
"BJ": 27,
|
||||
"LK": 127,
|
||||
"NA": 155,
|
||||
"AS": 14,
|
||||
"SG": 192,
|
||||
"PE": 169,
|
||||
"IR": 0,
|
||||
"MX": 152,
|
||||
"TD": 207,
|
||||
"AZ": 18,
|
||||
"AM": 9,
|
||||
"BL": 0,
|
||||
"SJ": 195,
|
||||
"SB": 188,
|
||||
"NF": 158,
|
||||
"RS": 239,
|
||||
"DE": 56,
|
||||
"EH": 65,
|
||||
"EE": 63,
|
||||
"SD": 190,
|
||||
"ML": 140,
|
||||
"TC": 206,
|
||||
"MZ": 154,
|
||||
"BS": 32,
|
||||
"UY": 226,
|
||||
"SI": 194,
|
||||
"AI": 7
|
||||
}
|
||||
|
||||
|
||||
def getCountryID(code):
|
||||
"""
|
||||
Get country ID for osu client
|
||||
|
||||
code -- country name abbreviation (eg: US)
|
||||
return -- country code int
|
||||
"""
|
||||
|
||||
if code in countryCodes:
|
||||
return countryCodes[code]
|
||||
else:
|
||||
return 0
|
||||
|
||||
def getCountryLetters(code):
|
||||
"""
|
||||
Get country letters from osu country ID
|
||||
|
||||
code -- country code int
|
||||
return -- country name (2 letters) (XX if code not found)
|
||||
"""
|
||||
|
||||
for key, value in countryCodes.items():
|
||||
if value == code:
|
||||
return key
|
||||
|
||||
return "XX"
|
44
createMatchEvent.py
Normal file
44
createMatchEvent.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
import serverPackets
|
||||
import clientPackets
|
||||
import glob
|
||||
import consoleHelper
|
||||
import bcolors
|
||||
import joinMatchEvent
|
||||
import exceptions
|
||||
|
||||
def handle(userToken, packetData):
|
||||
try:
|
||||
# get usertoken data
|
||||
userID = userToken.userID
|
||||
|
||||
# Read packet data
|
||||
packetData = clientPackets.createMatch(packetData)
|
||||
|
||||
# Create a match object
|
||||
# TODO: Player number check
|
||||
matchID = glob.matches.createMatch(packetData["matchName"], packetData["matchPassword"], packetData["beatmapID"], packetData["beatmapName"], packetData["beatmapMD5"], packetData["gameMode"], userID)
|
||||
|
||||
# Make sure the match has been created
|
||||
if matchID not in glob.matches.matches:
|
||||
raise exceptions.matchCreateError
|
||||
|
||||
# Get match object
|
||||
match = glob.matches.matches[matchID]
|
||||
|
||||
# Join that match
|
||||
joinMatchEvent.joinMatch(userToken, matchID, packetData["matchPassword"])
|
||||
|
||||
# Give host to match creator
|
||||
match.setHost(userID)
|
||||
|
||||
# Send match create packet to everyone in lobby
|
||||
for i in glob.matches.usersInLobby:
|
||||
# Make sure this user is still connected
|
||||
token = glob.tokens.getTokenFromUserID(i)
|
||||
if token != None:
|
||||
token.enqueue(serverPackets.createMatch(matchID))
|
||||
|
||||
# Console output
|
||||
consoleHelper.printColored("> MPROOM{}: Room created!".format(matchID), bcolors.BLUE)
|
||||
except exceptions.matchCreateError:
|
||||
consoleHelper.printColored("[!] Error while creating match!", bcolors.RED)
|
302
crypt.py
Normal file
302
crypt.py
Normal file
|
@ -0,0 +1,302 @@
|
|||
# Huge thanks to Cairnarvon
|
||||
# https://gist.github.com/Cairnarvon/5075687
|
||||
|
||||
# Initial permutation
|
||||
IP = (
|
||||
58, 50, 42, 34, 26, 18, 10, 2,
|
||||
60, 52, 44, 36, 28, 20, 12, 4,
|
||||
62, 54, 46, 38, 30, 22, 14, 6,
|
||||
64, 56, 48, 40, 32, 24, 16, 8,
|
||||
57, 49, 41, 33, 25, 17, 9, 1,
|
||||
59, 51, 43, 35, 27, 19, 11, 3,
|
||||
61, 53, 45, 37, 29, 21, 13, 5,
|
||||
63, 55, 47, 39, 31, 23, 15, 7,
|
||||
)
|
||||
|
||||
# Final permutation, FP = IP^(-1)
|
||||
FP = (
|
||||
40, 8, 48, 16, 56, 24, 64, 32,
|
||||
39, 7, 47, 15, 55, 23, 63, 31,
|
||||
38, 6, 46, 14, 54, 22, 62, 30,
|
||||
37, 5, 45, 13, 53, 21, 61, 29,
|
||||
36, 4, 44, 12, 52, 20, 60, 28,
|
||||
35, 3, 43, 11, 51, 19, 59, 27,
|
||||
34, 2, 42, 10, 50, 18, 58, 26,
|
||||
33, 1, 41, 9, 49, 17, 57, 25,
|
||||
)
|
||||
|
||||
# Permuted-choice 1 from the key bits to yield C and D.
|
||||
# Note that bits 8,16... are left out: They are intended for a parity check.
|
||||
PC1_C = (
|
||||
57, 49, 41, 33, 25, 17, 9,
|
||||
1, 58, 50, 42, 34, 26, 18,
|
||||
10, 2, 59, 51, 43, 35, 27,
|
||||
19, 11, 3, 60, 52, 44, 36,
|
||||
)
|
||||
PC1_D = (
|
||||
63, 55, 47, 39, 31, 23, 15,
|
||||
7, 62, 54, 46, 38, 30, 22,
|
||||
14, 6, 61, 53, 45, 37, 29,
|
||||
21, 13, 5, 28, 20, 12, 4,
|
||||
)
|
||||
|
||||
# Permuted-choice 2, to pick out the bits from the CD array that generate the
|
||||
# key schedule.
|
||||
PC2_C = (
|
||||
14, 17, 11, 24, 1, 5,
|
||||
3, 28, 15, 6, 21, 10,
|
||||
23, 19, 12, 4, 26, 8,
|
||||
16, 7, 27, 20, 13, 2,
|
||||
)
|
||||
PC2_D = (
|
||||
41, 52, 31, 37, 47, 55,
|
||||
30, 40, 51, 45, 33, 48,
|
||||
44, 49, 39, 56, 34, 53,
|
||||
46, 42, 50, 36, 29, 32,
|
||||
)
|
||||
|
||||
# The C and D arrays are used to calculate the key schedule.
|
||||
C = [0] * 28
|
||||
D = [0] * 28
|
||||
|
||||
# The key schedule. Generated from the key.
|
||||
KS = [[0] * 48 for _ in range(16)]
|
||||
|
||||
# The E bit-selection table.
|
||||
E = [0] * 48
|
||||
e2 = (
|
||||
32, 1, 2, 3, 4, 5,
|
||||
4, 5, 6, 7, 8, 9,
|
||||
8, 9, 10, 11, 12, 13,
|
||||
12, 13, 14, 15, 16, 17,
|
||||
16, 17, 18, 19, 20, 21,
|
||||
20, 21, 22, 23, 24, 25,
|
||||
24, 25, 26, 27, 28, 29,
|
||||
28, 29, 30, 31, 32, 1,
|
||||
)
|
||||
|
||||
# S-boxes.
|
||||
S = (
|
||||
(
|
||||
14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
|
||||
0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
|
||||
4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
|
||||
15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13
|
||||
),
|
||||
(
|
||||
15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
|
||||
3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
|
||||
0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
|
||||
13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9
|
||||
),
|
||||
(
|
||||
10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
|
||||
13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
|
||||
13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
|
||||
1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12
|
||||
),
|
||||
(
|
||||
7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
|
||||
13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
|
||||
10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
|
||||
3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14
|
||||
),
|
||||
(
|
||||
2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
|
||||
14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
|
||||
4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
|
||||
11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3
|
||||
),
|
||||
(
|
||||
12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
|
||||
10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
|
||||
9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
|
||||
4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13
|
||||
),
|
||||
(
|
||||
4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
|
||||
13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
|
||||
1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
|
||||
6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12
|
||||
),
|
||||
(
|
||||
13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
|
||||
1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
|
||||
7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
|
||||
2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11
|
||||
)
|
||||
)
|
||||
|
||||
# P is a permutation on the selected combination of the current L and key.
|
||||
P = (
|
||||
16, 7, 20, 21,
|
||||
29, 12, 28, 17,
|
||||
1, 15, 23, 26,
|
||||
5, 18, 31, 10,
|
||||
2, 8, 24, 14,
|
||||
32, 27, 3, 9,
|
||||
19, 13, 30, 6,
|
||||
22, 11, 4, 25,
|
||||
)
|
||||
|
||||
# The combination of the key and the input, before selection.
|
||||
preS = [0] * 48
|
||||
|
||||
|
||||
def __setkey(key):
|
||||
"""
|
||||
Set up the key schedule from the encryption key.
|
||||
"""
|
||||
global C, D, KS, E
|
||||
|
||||
shifts = (1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1)
|
||||
|
||||
# First, generate C and D by permuting the key. The lower order bit of each
|
||||
# 8-bit char is not used, so C and D are only 28 bits apiece.
|
||||
for i in range(28):
|
||||
C[i] = key[PC1_C[i] - 1]
|
||||
D[i] = key[PC1_D[i] - 1]
|
||||
|
||||
for i in range(16):
|
||||
# rotate
|
||||
for k in range(shifts[i]):
|
||||
temp = C[0]
|
||||
|
||||
for j in range(27):
|
||||
C[j] = C[j + 1]
|
||||
|
||||
C[27] = temp
|
||||
temp = D[0]
|
||||
for j in range(27):
|
||||
D[j] = D[j + 1]
|
||||
|
||||
D[27] = temp
|
||||
|
||||
# get Ki. Note C and D are concatenated
|
||||
for j in range(24):
|
||||
KS[i][j] = C[PC2_C[j] - 1]
|
||||
KS[i][j + 24] = D[PC2_D[j] - 28 - 1]
|
||||
|
||||
# load E with the initial E bit selections
|
||||
for i in range(48):
|
||||
E[i] = e2[i]
|
||||
|
||||
def __encrypt(block):
|
||||
global preS
|
||||
|
||||
left, right = [], [] # block in two halves
|
||||
f = [0] * 32
|
||||
|
||||
# First, permute the bits in the input
|
||||
for j in range(32):
|
||||
left.append(block[IP[j] - 1])
|
||||
|
||||
for j in range(32, 64):
|
||||
right.append(block[IP[j] - 1])
|
||||
|
||||
# Perform an encryption operation 16 times.
|
||||
for i in range(16):
|
||||
# Save the right array, which will be the new left.
|
||||
old = right[:]
|
||||
|
||||
# Expand right to 48 bits using the E selector and exclusive-or with
|
||||
# the current key bits.
|
||||
for j in range(48):
|
||||
preS[j] = right[E[j] - 1] ^ KS[i][j]
|
||||
|
||||
# The pre-select bits are now considered in 8 groups of 6 bits each.
|
||||
# The 8 selection functions map these 6-bit quantities into 4-bit
|
||||
# quantities and the results are permuted to make an f(R, K).
|
||||
# The indexing into the selection functions is peculiar; it could be
|
||||
# simplified by rewriting the tables.
|
||||
for j in range(8):
|
||||
temp = 6 * j
|
||||
k = S[j][(preS[temp + 0] << 5) +
|
||||
(preS[temp + 1] << 3) +
|
||||
(preS[temp + 2] << 2) +
|
||||
(preS[temp + 3] << 1) +
|
||||
(preS[temp + 4] << 0) +
|
||||
(preS[temp + 5] << 4)]
|
||||
|
||||
temp = 4 * j
|
||||
f[temp + 0] = (k >> 3) & 1
|
||||
f[temp + 1] = (k >> 2) & 1
|
||||
f[temp + 2] = (k >> 1) & 1
|
||||
f[temp + 3] = (k >> 0) & 1
|
||||
|
||||
# The new right is left ^ f(R, K).
|
||||
# The f here has to be permuted first, though.
|
||||
for j in range(32):
|
||||
right[j] = left[j] ^ f[P[j] - 1]
|
||||
|
||||
# Finally the new left (the original right) is copied back.
|
||||
left = old
|
||||
|
||||
# The output left and right are reversed.
|
||||
left, right = right, left
|
||||
|
||||
# The final output gets the inverse permutation of the very original
|
||||
for j in range(64):
|
||||
i = FP[j]
|
||||
if i < 33:
|
||||
block[j] = left[i - 1]
|
||||
else:
|
||||
block[j] = right[i - 33]
|
||||
|
||||
return block
|
||||
|
||||
def crypt(pw, salt):
|
||||
iobuf = []
|
||||
|
||||
# break pw into 64 bits
|
||||
block = []
|
||||
for c in pw:
|
||||
c = ord(c)
|
||||
for j in range(7):
|
||||
block.append((c >> (6 - j)) & 1)
|
||||
block.append(0)
|
||||
block += [0] * (64 - len(block))
|
||||
|
||||
# set key based on pw
|
||||
__setkey(block)
|
||||
|
||||
for i in range(2):
|
||||
# store salt at beginning of results
|
||||
iobuf.append(salt[i])
|
||||
c = ord(salt[i])
|
||||
|
||||
if c > ord('Z'):
|
||||
c -= 6
|
||||
|
||||
if c > ord('9'):
|
||||
c -= 7
|
||||
|
||||
c -= ord('.')
|
||||
|
||||
# use salt to effect the E-bit selection
|
||||
for j in range(6):
|
||||
if (c >> j) & 1:
|
||||
E[6 * i + j], E[6 * i + j + 24] = E[6 * i + j + 24], E[6 * i + j]
|
||||
|
||||
# call DES encryption 25 times using pw as key and initial data = 0
|
||||
block = [0] * 66
|
||||
for i in range(25):
|
||||
block = __encrypt(block)
|
||||
|
||||
# format encrypted block for standard crypt(3) output
|
||||
for i in range(11):
|
||||
c = 0
|
||||
for j in range(6):
|
||||
c <<= 1
|
||||
c |= block[6 * i + j]
|
||||
|
||||
c += ord('.')
|
||||
if c > ord('9'):
|
||||
c += 7
|
||||
|
||||
if c > ord('Z'):
|
||||
c += 6
|
||||
|
||||
iobuf.append(chr(c))
|
||||
|
||||
return ''.join(iobuf)
|
12
dataTypes.py
Normal file
12
dataTypes.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
"""Bancho packets data types"""
|
||||
#TODO: Uppercase, maybe?
|
||||
byte = 0
|
||||
uInt16 = 1
|
||||
sInt16 = 2
|
||||
uInt32 = 3
|
||||
sInt32 = 4
|
||||
uInt64 = 5
|
||||
sInt64 = 6
|
||||
string = 7
|
||||
ffloat = 8 # because float is a keyword
|
||||
bbytes = 9
|
137
databaseHelper.py
Normal file
137
databaseHelper.py
Normal file
|
@ -0,0 +1,137 @@
|
|||
import pymysql
|
||||
import bcolors
|
||||
import consoleHelper
|
||||
import threading
|
||||
|
||||
class db:
|
||||
"""A MySQL database connection"""
|
||||
|
||||
connection = None
|
||||
disconnected = False
|
||||
pingTime = 600
|
||||
|
||||
def __init__(self, __host, __username, __password, __database, __pingTime = 600):
|
||||
"""
|
||||
Connect to MySQL database
|
||||
|
||||
__host -- MySQL host name
|
||||
__username -- MySQL username
|
||||
__password -- MySQL password
|
||||
__database -- MySQL database name
|
||||
__pingTime -- MySQL database ping time (default: 600)
|
||||
"""
|
||||
|
||||
self.connection = pymysql.connect(host=__host, user=__username, password=__password, db=__database, cursorclass=pymysql.cursors.DictCursor, autocommit=True)
|
||||
self.pingTime = __pingTime
|
||||
self.pingLoop()
|
||||
|
||||
|
||||
def bindParams(self, __query, __params):
|
||||
"""
|
||||
Replace every ? with the respective **escaped** parameter in array
|
||||
|
||||
__query -- query with ?s
|
||||
__params -- array with params
|
||||
|
||||
return -- new query
|
||||
"""
|
||||
|
||||
for i in __params:
|
||||
escaped = self.connection.escape(i)
|
||||
__query = __query.replace("?", str(escaped), 1)
|
||||
|
||||
return __query
|
||||
|
||||
|
||||
def execute(self, __query, __params = None):
|
||||
"""
|
||||
Execute a SQL query
|
||||
|
||||
__query -- query, can contain ?s
|
||||
__params -- array with params. Optional
|
||||
"""
|
||||
|
||||
|
||||
with self.connection.cursor() as cursor:
|
||||
try:
|
||||
# Bind params if needed
|
||||
if __params != None:
|
||||
__query = self.bindParams(__query, __params)
|
||||
|
||||
# Execute the query
|
||||
cursor.execute(__query)
|
||||
finally:
|
||||
# Close this connection
|
||||
cursor.close()
|
||||
|
||||
|
||||
def fetch(self, __query, __params = None, __all = False):
|
||||
"""
|
||||
Fetch the first (or all) element(s) of SQL query result
|
||||
|
||||
__query -- query, can contain ?s
|
||||
__params -- array with params. Optional
|
||||
__all -- if true, will fetch all values. Same as fetchAll
|
||||
|
||||
return -- dictionary with result data or False if failed
|
||||
"""
|
||||
|
||||
|
||||
with self.connection.cursor() as cursor:
|
||||
try:
|
||||
# Bind params if needed
|
||||
if __params != None:
|
||||
__query = self.bindParams(__query, __params)
|
||||
|
||||
# Execute the query with binded params
|
||||
cursor.execute(__query)
|
||||
|
||||
# Get first result and return it
|
||||
if __all == False:
|
||||
return cursor.fetchone()
|
||||
else:
|
||||
return cursor.fetchall()
|
||||
finally:
|
||||
# Close this connection
|
||||
cursor.close()
|
||||
|
||||
|
||||
def fetchAll(self, __query, __params = None):
|
||||
"""
|
||||
Fetch the all elements of SQL query result
|
||||
|
||||
__query -- query, can contain ?s
|
||||
__params -- array with params. Optional
|
||||
|
||||
return -- dictionary with result data
|
||||
"""
|
||||
|
||||
return self.fetch(__query, __params, True)
|
||||
|
||||
def pingLoop(self):
|
||||
"""
|
||||
Pings MySQL server. We need to ping/execute a query at least once every 8 hours
|
||||
or the connection will die.
|
||||
If called once, will recall after 30 minutes and so on, forever
|
||||
CALL THIS FUNCTION ONLY ONCE!
|
||||
"""
|
||||
|
||||
# Default loop time
|
||||
time = self.pingTime
|
||||
|
||||
# Make sure the connection is alive
|
||||
try:
|
||||
# Try to ping and reconnect if not connected
|
||||
self.connection.ping()
|
||||
if self.disconnected == True:
|
||||
# If we were disconnected, set disconnected to false and print message
|
||||
self.disconnected = False
|
||||
consoleHelper.printColored("> Reconnected to MySQL server!", bcolors.GREEN)
|
||||
except:
|
||||
# Can't ping MySQL server. Show error and call loop in 5 seconds
|
||||
consoleHelper.printColored("[!] CRITICAL!! MySQL connection died! Make sure your MySQL server is running! Checking again in 5 seconds...", bcolors.RED)
|
||||
self.disconnected = True
|
||||
time = 5
|
||||
|
||||
# Schedule a new check (endless loop)
|
||||
threading.Timer(time, self.pingLoop).start()
|
58
exceptions.py
Normal file
58
exceptions.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
"""Bancho exceptions"""
|
||||
# TODO: Prints in exceptions
|
||||
class loginFailedException(Exception):
|
||||
pass
|
||||
|
||||
class loginBannedException(Exception):
|
||||
pass
|
||||
|
||||
class tokenNotFoundException(Exception):
|
||||
pass
|
||||
|
||||
class channelNoPermissionsException(Exception):
|
||||
pass
|
||||
|
||||
class channelUnknownException(Exception):
|
||||
pass
|
||||
|
||||
class channelModeratedException(Exception):
|
||||
pass
|
||||
|
||||
class noAdminException(Exception):
|
||||
pass
|
||||
|
||||
class commandSyntaxException(Exception):
|
||||
pass
|
||||
|
||||
class banchoConfigErrorException(Exception):
|
||||
pass
|
||||
|
||||
class banchoMaintenanceException(Exception):
|
||||
pass
|
||||
|
||||
class moderatedPMException(Exception):
|
||||
pass
|
||||
|
||||
class userNotFoundException(Exception):
|
||||
pass
|
||||
|
||||
class alreadyConnectedException(Exception):
|
||||
pass
|
||||
|
||||
class stopSpectating(Exception):
|
||||
pass
|
||||
|
||||
class matchWrongPasswordException(Exception):
|
||||
pass
|
||||
|
||||
class matchNotFoundException(Exception):
|
||||
pass
|
||||
|
||||
class matchJoinErrorException(Exception):
|
||||
pass
|
||||
|
||||
class matchCreateError(Exception):
|
||||
pass
|
||||
|
||||
class banchoRestartingException(Exception):
|
||||
pass
|
55
fokabot.py
Normal file
55
fokabot.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
"""FokaBot related functions"""
|
||||
import userHelper
|
||||
import glob
|
||||
import actions
|
||||
import serverPackets
|
||||
import fokabotCommands
|
||||
|
||||
def connect():
|
||||
"""Add FokaBot to connected users and send userpanel/stats packet to everyone"""
|
||||
|
||||
token = glob.tokens.addToken(999)
|
||||
token.actionID = actions.idle
|
||||
glob.tokens.enqueueAll(serverPackets.userPanel(999))
|
||||
glob.tokens.enqueueAll(serverPackets.userStats(999))
|
||||
|
||||
def disconnect():
|
||||
"""Remove FokaBot from connected users"""
|
||||
|
||||
glob.tokens.deleteToken(glob.tokens.getTokenFromUserID(999))
|
||||
|
||||
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
|
||||
"""
|
||||
|
||||
for i in fokabotCommands.commands:
|
||||
# Loop though all commands
|
||||
if i["trigger"] in message:
|
||||
# message has triggered a command
|
||||
|
||||
# Make sure the user has right permissions
|
||||
if i["minRank"] > 1:
|
||||
# Get rank from db only if minrank > 1, so we save some CPU
|
||||
if userHelper.getRankPrivileges(userHelper.getID(fro)) < i["minRank"]:
|
||||
return False
|
||||
|
||||
# Check argument number
|
||||
message = message.split(" ")
|
||||
if i["syntax"] != "" and len(message) <= len(i["syntax"].split(" ")):
|
||||
return "Wrong syntax: {} {}".format(i["trigger"], i["syntax"])
|
||||
|
||||
# Return response or execute callback
|
||||
if i["callback"] == None:
|
||||
return i["response"]
|
||||
else:
|
||||
return i["callback"](fro, chan, message[1:])
|
||||
|
||||
# No commands triggered
|
||||
return False
|
355
fokabotCommands.py
Normal file
355
fokabotCommands.py
Normal file
|
@ -0,0 +1,355 @@
|
|||
import fokabot
|
||||
import random
|
||||
import glob
|
||||
import serverPackets
|
||||
import exceptions
|
||||
import userHelper
|
||||
import time
|
||||
import systemHelper
|
||||
|
||||
"""
|
||||
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
|
||||
. . .
|
||||
|
||||
return the message or **False** if there's no response by the bot
|
||||
"""
|
||||
|
||||
def faq(fro, chan, message):
|
||||
if message[0] == "rules":
|
||||
return "Please make sure to check (Ripple's rules)[http://ripple.moe/?p=23]."
|
||||
elif message[0] == "rules":
|
||||
return "Please make sure to check (Ripple's rules)[http://ripple.moe/?p=23]."
|
||||
elif message[0] == "swearing":
|
||||
return "Please don't abuse swearing"
|
||||
elif message[0] == "spam":
|
||||
return "Please don't spam"
|
||||
elif message[0] == "offend":
|
||||
return "Please don't offend other players"
|
||||
elif message[0] == "github":
|
||||
return "(Ripple's Github page!)[https://github.com/osuripple/ripple]"
|
||||
elif message[0] == "discord":
|
||||
return "(Join Ripple's Discord!)[https://discord.gg/0rJcZruIsA6rXuIx]"
|
||||
elif message[0] == "blog":
|
||||
return "You can find the latest Ripple news on the (blog)[https://ripple.moe/blog/]!"
|
||||
elif message[0] == "changelog":
|
||||
return "Check the (changelog)[https://ripple.moe/index.php?p=17] !"
|
||||
elif message[0] == "status":
|
||||
return "Check the server status (here!)[https://ripple.moe/index.php?p=27]"
|
||||
|
||||
def roll(fro, chan, message):
|
||||
maxPoints = 100
|
||||
if len(message) >= 1:
|
||||
if message[0].isdigit() == True and int(message[0]) > 0:
|
||||
maxPoints = int(message[0])
|
||||
|
||||
points = random.randrange(0,maxPoints)
|
||||
return "{} rolls {} points!".format(fro, str(points))
|
||||
|
||||
def ask(fro, chan, message):
|
||||
return random.choice(["yes", "no", "maybe"])
|
||||
|
||||
def alert(fro, chan, message):
|
||||
glob.tokens.enqueueAll(serverPackets.notification(' '.join(message[:])))
|
||||
return False
|
||||
|
||||
def moderated(fro, chan, message):
|
||||
try:
|
||||
# Make sure we are in a channel and not PM
|
||||
if chan.startswith("#") == False:
|
||||
raise exceptions.moderatedPMException
|
||||
|
||||
# Get on/off
|
||||
enable = True
|
||||
if len(message) >= 1:
|
||||
if message[0] == "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.moderatedPMException:
|
||||
return "You are trying to put a private chat in moderated mode. Are you serious?!? You're fired."
|
||||
|
||||
def kickAll(fro, chan, message):
|
||||
# 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."
|
||||
|
||||
def kick(fro, chan, message):
|
||||
# Get parameters
|
||||
target = message[0].replace("_", " ")
|
||||
|
||||
# Get target token and make sure is connected
|
||||
targetToken = glob.tokens.getTokenFromUsername(target)
|
||||
if targetToken == None:
|
||||
return "{} is not online".format(target)
|
||||
|
||||
# Kick user
|
||||
targetToken.kick()
|
||||
|
||||
# Bot response
|
||||
return "{} has been kicked from the server.".format(target)
|
||||
|
||||
def fokabotReconnect(fro, chan, message):
|
||||
# Check if fokabot is already connected
|
||||
if glob.tokens.getTokenFromUserID(999) != None:
|
||||
return"Fokabot is already connected to Bancho"
|
||||
|
||||
# Fokabot is not connected, connect it
|
||||
fokabot.connect()
|
||||
return False
|
||||
|
||||
def silence(fro, chan, message):
|
||||
for i in message:
|
||||
i = i.lower()
|
||||
target = message[0].replace("_", " ")
|
||||
amount = message[1]
|
||||
unit = message[2]
|
||||
reason = ' '.join(message[3:])
|
||||
|
||||
# Get target user ID
|
||||
targetUserID = userHelper.getID(target)
|
||||
|
||||
# Make sure the user exists
|
||||
if targetUserID == False:
|
||||
return "{}: user not found".format(target)
|
||||
|
||||
# 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:
|
||||
return "Invalid time unit (s/m/h/d)."
|
||||
|
||||
# Max silence time is 7 days
|
||||
if silenceTime > 604800:
|
||||
return "Invalid silence time. Max silence time is 7 days."
|
||||
|
||||
# Calculate silence end time
|
||||
endTime = int(time.time())+silenceTime
|
||||
|
||||
# Update silence end in db
|
||||
userHelper.silence(targetUserID, endTime, reason)
|
||||
|
||||
# Send silence packet to target if he's connected
|
||||
targetToken = glob.tokens.getTokenFromUsername(target)
|
||||
if targetToken != None:
|
||||
targetToken.enqueue(serverPackets.silenceEndTime(silenceTime))
|
||||
|
||||
return "{} has been silenced for the following reason: {}".format(target, reason)
|
||||
|
||||
def removeSilence(fro, chan, message):
|
||||
# Get parameters
|
||||
for i in message:
|
||||
i = i.lower()
|
||||
target = message[0].replace("_", " ")
|
||||
|
||||
# Make sure the user exists
|
||||
targetUserID = userHelper.getID(target)
|
||||
if targetUserID == False:
|
||||
return "{}: user not found".format(target)
|
||||
|
||||
# Reset user silence time and reason in db
|
||||
userHelper.silence(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)
|
||||
|
||||
def restartShutdown(restart):
|
||||
"""Restart (if restart = True) or shutdown (if restart = False) pep.py safely"""
|
||||
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
|
||||
|
||||
def systemRestart(fro, chan, message):
|
||||
return restartShutdown(True)
|
||||
|
||||
def systemShutdown(fro, chan, message):
|
||||
return restartShutdown(False)
|
||||
|
||||
def systemReload(fro, chan, message):
|
||||
#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.mainMenuIcon(glob.banchoConf.config["menuIcon"]))
|
||||
glob.tokens.enqueueAll(serverPackets.channelInfoEnd())
|
||||
for key, _ in glob.channels.channels.items():
|
||||
glob.tokens.enqueueAll(serverPackets.channelInfo(key))
|
||||
|
||||
return "Bancho settings reloaded!"
|
||||
|
||||
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 systemStatus(fro, chan, message):
|
||||
# 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
|
||||
|
||||
"""
|
||||
Commands list
|
||||
|
||||
trigger: message that triggers the command
|
||||
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: <arg1> <arg2>)
|
||||
minRank: minimum rank to execute that command. Optional (default = 1)
|
||||
|
||||
You MUST set trigger and callback/response, or the command won't work.
|
||||
"""
|
||||
commands = [
|
||||
{
|
||||
"trigger": "!roll",
|
||||
"callback": roll
|
||||
}, {
|
||||
"trigger": "!faq",
|
||||
"syntax": "<name>",
|
||||
"callback": faq
|
||||
}, {
|
||||
"trigger": "!report",
|
||||
"response": "Report command isn't here yet :c"
|
||||
}, {
|
||||
"trigger": "!help",
|
||||
"response": "Click (here)[https://ripple.moe/index.php?p=16&id=4] for FokaBot's full command list"
|
||||
}, {
|
||||
"trigger": "!ask",
|
||||
"syntax": "<question>",
|
||||
"callback": ask
|
||||
}, {
|
||||
"trigger": "!mm00",
|
||||
"response": random.choice(["meme", "MA MAURO ESISTE?"])
|
||||
}, {
|
||||
"trigger": "!alert",
|
||||
"syntax": "<message>",
|
||||
"minRank": 4,
|
||||
"callback": alert
|
||||
}, {
|
||||
"trigger": "!moderated",
|
||||
"minRank": 3,
|
||||
"callback": moderated
|
||||
}, {
|
||||
"trigger": "!kickall",
|
||||
"minRank": 4,
|
||||
"callback": kickAll
|
||||
}, {
|
||||
"trigger": "!kick",
|
||||
"syntax": "<target>",
|
||||
"minRank": 3,
|
||||
"callback": kick
|
||||
}, {
|
||||
"trigger": "!fokabot reconnect",
|
||||
"minRank": 3,
|
||||
"callback": fokabotReconnect
|
||||
}, {
|
||||
"trigger": "!silence",
|
||||
"syntax": "<target> <amount> <unit(s/m/h/d)> <reason>",
|
||||
"minRank": 3,
|
||||
"callback": silence
|
||||
}, {
|
||||
"trigger": "!removesilence",
|
||||
"syntax": "<target>",
|
||||
"minRank": 3,
|
||||
"callback": removeSilence
|
||||
}, {
|
||||
"trigger": "!system restart",
|
||||
"minRank": 4,
|
||||
"callback": systemRestart
|
||||
}, {
|
||||
"trigger": "!system shutdown",
|
||||
"minRank": 4,
|
||||
"callback": systemShutdown
|
||||
}, {
|
||||
"trigger": "!system reload",
|
||||
"minRank": 3,
|
||||
"callback": systemReload
|
||||
}, {
|
||||
"trigger": "!system maintenance",
|
||||
"minRank": 3,
|
||||
"callback": systemMaintenance
|
||||
}, {
|
||||
"trigger": "!system status",
|
||||
"minRank": 3,
|
||||
"callback": systemStatus
|
||||
}
|
||||
]
|
||||
|
||||
# Commands list default values
|
||||
for cmd in commands:
|
||||
cmd.setdefault("syntax", "")
|
||||
cmd.setdefault("minRank", 1)
|
||||
cmd.setdefault("callback", None)
|
||||
cmd.setdefault("response", "u w0t m8?")
|
10
friendAddEvent.py
Normal file
10
friendAddEvent.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
import userHelper
|
||||
import clientPackets
|
||||
|
||||
def handle(userToken, packetData):
|
||||
# Friend add packet
|
||||
packetData = clientPackets.addRemoveFriend(packetData)
|
||||
userHelper.addFriend(userToken.userID, packetData["friendID"])
|
||||
|
||||
# Console output
|
||||
print("> {} have added {} to their friends".format(userToken.username, str(packetData["friendID"])))
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user