Moved pep.py to another repo

This commit is contained in:
Nyo 2016-04-19 19:40:59 +02:00
commit 47305d612a
153 changed files with 5942 additions and 0 deletions

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
actions.py Normal file
View 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
View 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
View 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
View 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
View 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
View 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"])

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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