Moved pep.py to another repo
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+17
@@ -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
|
||||
@@ -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)])
|
||||
@@ -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'
|
||||
@@ -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()
|
||||
@@ -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))
|
||||
@@ -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"])
|
||||
@@ -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"])
|
||||
@@ -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)
|
||||
@@ -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
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
@@ -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
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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
@@ -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
|
||||
@@ -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?")
|
||||
@@ -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
Reference in New Issue
Block a user