Compare commits

..

No commits in common. "master" and "tornado-gevent" have entirely different histories.

117 changed files with 4406 additions and 4831 deletions

6
.gitignore vendored
View File

@ -1,10 +1,4 @@
**/__pycache__ **/__pycache__
**/build
config.ini config.ini
filters.txt filters.txt
.data .data
.idea
redistest.py
*.c
*.so
.pyenv

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "common"]
path = common
url = https://github.com/osufx/ripple-python-common.git

View File

@ -1,7 +0,0 @@
python-targets:
- 3
pep8:
none: true
pylint:
disable:
- cyclic-import

View File

@ -1,8 +1,4 @@
## pep.py [![Code Health](https://landscape.io/github/osuripple/pep.py/master/landscape.svg?style=flat)](https://landscape.io/github/osuripple/pep.py/master) ## pep.py
- Origin: https://git.zxq.co/ripple/pep.py
- Mirror: https://github.com/osuripple/pep.py
This is Ripple's bancho server. It handles: This is Ripple's bancho server. It handles:
- Client login - Client login
- Online users listing and statuses - Online users listing and statuses
@ -13,30 +9,19 @@ This is Ripple's bancho server. It handles:
## Requirements ## Requirements
- Python 3.5 - Python 3.5
- Cython - MySQLdb (`mysqlclient` or `mysql-python`)
- C compiler
- MySQLdb (`mysqlclient`)
- Tornado - Tornado
- Gevent
- Bcrypt - Bcrypt
- Raven
## How to set up pep.py ## How to set up pep.py
First of all, initialize and update the submodules First of all, install all the dependencies
``` ```
$ git submodule init && git submodule update $ pip install mysqlclient tornado gevent bcrypt
``` ```
afterwards, install the required dependencies with pip then, run pep.py once to create the default config file and edit it
```
$ pip install -r requirements.txt
```
then, compile all `*.pyx` files to `*.so` or `*.dll` files using `setup.py` (distutils file)
```
$ python3 setup.py build_ext --inplace
```
finally, run pep.py once to create the default config file and edit it
``` ```
$ python3 pep.py $ python3 pep.py
...
$ nano config.ini $ nano config.ini
``` ```
you can run pep.py by typing you can run pep.py by typing

1
common

@ -1 +0,0 @@
Subproject commit 6103fe96a79cd8f5cbabe24b5fac9cf2a5cacb4a

17
constants/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

9
constants/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'

View File

@ -1,3 +1,4 @@
""" Contains functions used to read specific client packets from byte stream """
from constants import dataTypes from constants import dataTypes
from helpers import packetHelper from helpers import packetHelper
from constants import slotStatuses from constants import slotStatuses
@ -7,86 +8,86 @@ from constants import slotStatuses
def userActionChange(stream): def userActionChange(stream):
return packetHelper.readPacketData(stream, return packetHelper.readPacketData(stream,
[ [
["actionID", dataTypes.BYTE], ["actionID", dataTypes.byte],
["actionText", dataTypes.STRING], ["actionText", dataTypes.string],
["actionMd5", dataTypes.STRING], ["actionMd5", dataTypes.string],
["actionMods", dataTypes.UINT32], ["actionMods", dataTypes.uInt32],
["gameMode", dataTypes.BYTE], ["gameMode", dataTypes.byte]
["beatmapID", dataTypes.SINT32]
]) ])
def userStatsRequest(stream): def userStatsRequest(stream):
return packetHelper.readPacketData(stream, [["users", dataTypes.INT_LIST]]) return packetHelper.readPacketData(stream, [["users", dataTypes.intList]])
def userPanelRequest(stream): def userPanelRequest(stream):
return packetHelper.readPacketData(stream, [["users", dataTypes.INT_LIST]]) return packetHelper.readPacketData(stream, [["users", dataTypes.intList]])
""" Client chat packets """ """ Client chat packets """
def sendPublicMessage(stream): def sendPublicMessage(stream):
return packetHelper.readPacketData(stream, return packetHelper.readPacketData(stream,
[ [
["unknown", dataTypes.STRING], ["unknown", dataTypes.string],
["message", dataTypes.STRING], ["message", dataTypes.string],
["to", dataTypes.STRING] ["to", dataTypes.string]
]) ])
def sendPrivateMessage(stream): def sendPrivateMessage(stream):
return packetHelper.readPacketData(stream, return packetHelper.readPacketData(stream,
[ [
["unknown", dataTypes.STRING], ["unknown", dataTypes.string],
["message", dataTypes.STRING], ["message", dataTypes.string],
["to", dataTypes.STRING], ["to", dataTypes.string],
["unknown2", dataTypes.UINT32] ["unknown2", dataTypes.uInt32]
]) ])
def setAwayMessage(stream): def setAwayMessage(stream):
return packetHelper.readPacketData(stream, return packetHelper.readPacketData(stream,
[ [
["unknown", dataTypes.STRING], ["unknown", dataTypes.string],
["awayMessage", dataTypes.STRING] ["awayMessage", dataTypes.string]
]) ])
def channelJoin(stream): def channelJoin(stream):
return packetHelper.readPacketData(stream, [["channel", dataTypes.STRING]]) return packetHelper.readPacketData(stream,[["channel", dataTypes.string]])
def channelPart(stream): def channelPart(stream):
return packetHelper.readPacketData(stream, [["channel", dataTypes.STRING]]) return packetHelper.readPacketData(stream,[["channel", dataTypes.string]])
def addRemoveFriend(stream): def addRemoveFriend(stream):
return packetHelper.readPacketData(stream, [["friendID", dataTypes.SINT32]]) return packetHelper.readPacketData(stream, [["friendID", dataTypes.sInt32]])
""" Spectator packets """
""" SPECTATOR PACKETS """
def startSpectating(stream): def startSpectating(stream):
return packetHelper.readPacketData(stream, [["userID", dataTypes.SINT32]]) return packetHelper.readPacketData(stream,[["userID", dataTypes.sInt32]])
""" Multiplayer packets """ """ MULTIPLAYER PACKETS """
def matchSettings(stream): def matchSettings(stream):
# Data to return, will be merged later # Data to return, will be merged later
data = [] data = []
# Some settings # Some settings
struct = [ struct = [
["matchID", dataTypes.UINT16], ["matchID", dataTypes.uInt16],
["inProgress", dataTypes.BYTE], ["inProgress", dataTypes.byte],
["unknown", dataTypes.BYTE], ["unknown", dataTypes.byte],
["mods", dataTypes.UINT32], ["mods", dataTypes.uInt32],
["matchName", dataTypes.STRING], ["matchName", dataTypes.string],
["matchPassword", dataTypes.STRING], ["matchPassword", dataTypes.string],
["beatmapName", dataTypes.STRING], ["beatmapName", dataTypes.string],
["beatmapID", dataTypes.UINT32], ["beatmapID", dataTypes.uInt32],
["beatmapMD5", dataTypes.STRING] ["beatmapMD5", dataTypes.string]
] ]
# Slot statuses (not used) # Slot statuses (not used)
for i in range(0,16): for i in range(0,16):
struct.append(["slot{}Status".format(str(i)), dataTypes.BYTE]) struct.append(["slot{}Status".format(str(i)), dataTypes.byte])
# Slot statuses (not used) # Slot statuses (not used)
for i in range(0,16): for i in range(0,16):
struct.append(["slot{}Team".format(str(i)), dataTypes.BYTE]) struct.append(["slot{}Team".format(str(i)), dataTypes.byte])
# Read first part # Read first part
data.append(packetHelper.readPacketData(stream, struct)) data.append(packetHelper.readPacketData(stream, struct))
@ -99,21 +100,24 @@ def matchSettings(stream):
start += 2 start += 2
for i in range(0,16): for i in range(0,16):
s = data[0]["slot{}Status".format(str(i))] s = data[0]["slot{}Status".format(str(i))]
if s != slotStatuses.FREE and s != slotStatuses.LOCKED: if s != slotStatuses.free and s != slotStatuses.locked:
start += 4 start += 4
# Other settings # Other settings
struct = [ struct = [
["hostUserID", dataTypes.SINT32], ["hostUserID", dataTypes.sInt32],
["gameMode", dataTypes.BYTE], ["gameMode", dataTypes.byte],
["scoringType", dataTypes.BYTE], ["scoringType", dataTypes.byte],
["teamType", dataTypes.BYTE], ["teamType", dataTypes.byte],
["freeMods", dataTypes.BYTE], ["freeMods", dataTypes.byte],
] ]
# Read last part # Read last part
data.append(packetHelper.readPacketData(stream[start:], struct, False)) data.append(packetHelper.readPacketData(stream[start:], struct, False))
# Mods if freemod (not used)
#if data[1]["freeMods"] == 1:
result = {} result = {}
for i in data: for i in data:
result.update(i) result.update(i)
@ -126,48 +130,19 @@ def changeMatchSettings(stream):
return matchSettings(stream) return matchSettings(stream)
def changeSlot(stream): def changeSlot(stream):
return packetHelper.readPacketData(stream, [["slotID", dataTypes.UINT32]]) return packetHelper.readPacketData(stream, [["slotID", dataTypes.uInt32]])
def joinMatch(stream): def joinMatch(stream):
return packetHelper.readPacketData(stream, [["matchID", dataTypes.UINT32], ["password", dataTypes.STRING]]) return packetHelper.readPacketData(stream, [["matchID", dataTypes.uInt32], ["password", dataTypes.string]])
def changeMods(stream): def changeMods(stream):
return packetHelper.readPacketData(stream, [["mods", dataTypes.UINT32]]) return packetHelper.readPacketData(stream, [["mods", dataTypes.uInt32]])
def lockSlot(stream): def lockSlot(stream):
return packetHelper.readPacketData(stream, [["slotID", dataTypes.UINT32]]) return packetHelper.readPacketData(stream, [["slotID", dataTypes.uInt32]])
def transferHost(stream): def transferHost(stream):
return packetHelper.readPacketData(stream, [["slotID", dataTypes.UINT32]]) return packetHelper.readPacketData(stream, [["slotID", dataTypes.uInt32]])
def matchInvite(stream): def matchInvite(stream):
return packetHelper.readPacketData(stream, [["userID", dataTypes.UINT32]]) return packetHelper.readPacketData(stream, [["userID", dataTypes.uInt32]])
def matchFrames(stream):
return packetHelper.readPacketData(stream,
[
["time", dataTypes.SINT32],
["id", dataTypes.BYTE],
["count300", dataTypes.UINT16],
["count100", dataTypes.UINT16],
["count50", dataTypes.UINT16],
["countGeki", dataTypes.UINT16],
["countKatu", dataTypes.UINT16],
["countMiss", dataTypes.UINT16],
["totalScore", dataTypes.SINT32],
["maxCombo", dataTypes.UINT16],
["currentCombo", dataTypes.UINT16],
["perfect", dataTypes.BYTE],
["currentHp", dataTypes.BYTE],
["tagByte", dataTypes.BYTE],
["usingScoreV2", dataTypes.BYTE]
])
def tournamentMatchInfoRequest(stream):
return packetHelper.readPacketData(stream, [["matchID", dataTypes.UINT32]])
def tournamentJoinMatchChannel(stream):
return packetHelper.readPacketData(stream, [["matchID", dataTypes.UINT32]])
def tournamentLeaveMatchChannel(stream):
return packetHelper.readPacketData(stream, [["matchID", dataTypes.UINT32]])

View File

@ -1,12 +1,13 @@
"""Bancho packets data types""" """Bancho packets data types"""
BYTE = 0 #TODO: Uppercase, maybe?
UINT16 = 1 byte = 0
SINT16 = 2 uInt16 = 1
UINT32 = 3 sInt16 = 2
SINT32 = 4 uInt32 = 3
UINT64 = 5 sInt32 = 4
SINT64 = 6 uInt64 = 5
STRING = 7 sInt64 = 6
FFLOAT = 8 # because float is a keyword string = 7
BBYTES = 9 ffloat = 8 # because float is a keyword
INT_LIST = 10 # TODO: Maybe there are some packets that still use uInt16 + uInt32 thing somewhere. bbytes = 9
intList = 10 # TODO: Maybe there are some packets that still use uInt16 + uInt32 thing somewhere.

View File

@ -1,3 +1,5 @@
"""Bancho exceptions"""
# TODO: Prints in exceptions
class loginFailedException(Exception): class loginFailedException(Exception):
pass pass
@ -81,30 +83,3 @@ class haxException(Exception):
class forceUpdateException(Exception): class forceUpdateException(Exception):
pass pass
class loginLockedException(Exception):
pass
class unknownStreamException(Exception):
pass
class userTournamentException(Exception):
pass
class userAlreadyInChannelException(Exception):
pass
class userNotInChannelException(Exception):
pass
class missingReportInfoException(Exception):
pass
class invalidUserException(Exception):
pass
class wrongChannelException(Exception):
pass
class periodicLoopException(Exception):
pass

File diff suppressed because it is too large Load Diff

41
constants/gameModes.py Normal file
View File

@ -0,0 +1,41 @@
"""Contains readable gamemodes with their codes"""
std = 0
taiko = 1
ctb = 2
mania = 3
def getGameModeForDB(gameMode):
"""
Convert a gamemode number to string for database table/column
gameMode -- gameMode int or variable (ex: gameMode.std)
return -- game mode readable string for db
"""
if gameMode == std:
return "std"
elif gameMode == taiko:
return "taiko"
elif gameMode == ctb:
return "ctb"
else:
return "mania"
def getGameModeForPrinting(gameMode):
"""
Convert a gamemode number to string for showing to a user (e.g. !last)
gameMode -- gameMode int or variable (ex: gameMode.std)
return -- game mode readable string for a human
"""
if gameMode == std:
return "osu!"
elif gameMode == taiko:
return "Taiko"
elif gameMode == ctb:
return "CatchTheBeat"
else:
return "osu!mania"

View File

@ -1,2 +1,2 @@
NORMAL = 0 normal = 0
FREE_MOD = 1 freeMod = 1

View File

@ -1,4 +1,3 @@
SCORE = 0 score = 0
ACCURACY = 1 accuracy = 1
COMBO = 2 combo = 2
SCORE_V2 = 3

View File

@ -1,4 +1,4 @@
HEAD_TO_HEAD = 0 headToHead = 0
TAG_COOP = 1 tagCoop = 1
TEAM_VS = 2 teamVs = 2
TAG_TEAM_VS = 3 tagTeamVs = 3

View File

@ -1,3 +1,3 @@
NO_TEAM = 0 noTeam = 0
BLUE = 1 blue = 1
RED = 2 red = 2

30
constants/mods.py Normal file
View File

@ -0,0 +1,30 @@
Nomod = 0
NoFail = 1
Easy = 2
NoVideo = 4
Hidden = 8
HardRock = 16
SuddenDeath = 32
DoubleTime = 64
Relax = 128
HalfTime = 256
Nightcore = 512
Flashlight = 1024
Autoplay = 2048
SpunOut = 4096
Relax2 = 8192
Perfect = 16384
Key4 = 32768
Key5 = 65536
Key6 = 131072
Key7 = 262144
Key8 = 524288
keyMod = 1015808
FadeIn = 1048576
Random = 2097152
LastMod = 4194304
Key9 = 16777216
Key10 = 33554432
Key1 = 67108864
Key3 = 134217728
Key2 = 268435456

View File

@ -74,11 +74,7 @@ server_channelInfoEnd = 89
client_matchChangePassword = 90 client_matchChangePassword = 90
server_matchChangePassword = 91 server_matchChangePassword = 91
server_silenceEnd = 92 server_silenceEnd = 92
client_specialMatchInfoRequest = 93
server_userSilenced = 94 server_userSilenced = 94
server_userPresenceBundle = 96 server_userPresenceBundle = 96
client_userPanelRequest = 97 client_userPanelRequest = 97
client_tournamentMatchInfoRequest = 93
server_matchAbort = 106
server_switchServer = 107
client_tournamentJoinMatchChannel = 108
client_tournamentLeaveMatchChannel = 109

21
constants/privileges.py Normal file
View File

@ -0,0 +1,21 @@
USER_PUBLIC = 1
USER_NORMAL = 2 << 0
USER_DONOR = 2 << 1
ADMIN_ACCESS_RAP = 2 << 2
ADMIN_MANAGE_USERS = 2 << 3
ADMIN_BAN_USERS = 2 << 4
ADMIN_SILENCE_USERS = 2 << 5
ADMIN_WIPE_USERS = 2 << 6
ADMIN_MANAGE_BEATMAPS = 2 << 7
ADMIN_MANAGE_SERVERS = 2 << 8
ADMIN_MANAGE_SETTINGS = 2 << 9
ADMIN_MANAGE_BETAKEYS = 2 << 10
ADMIN_MANAGE_REPORTS = 2 << 11
ADMIN_MANAGE_DOCS = 2 << 12
ADMIN_MANAGE_BADGES = 2 << 13
ADMIN_VIEW_RAP_LOGS = 2 << 14
ADMIN_MANAGE_PRIVILEGES = 2 << 15
ADMIN_SEND_ALERTS = 2 << 16
ADMIN_CHAT_MOD = 2 << 17
ADMIN_KICK_USERS = 2 << 18
USER_PENDING_VERIFICATION = 2 << 19

View File

@ -1,65 +1,69 @@
""" Contains functions used to write specific server packets to byte streams """ """ Contains functions used to write specific server packets to byte streams """
from common.constants import privileges
from common.ripple import userUtils
from constants import dataTypes
from constants import packetIDs
from constants import userRanks
from helpers import packetHelper from helpers import packetHelper
from constants import dataTypes
from helpers import userHelper
from objects import glob from objects import glob
from constants import userRanks
from constants import packetIDs
""" Login errors packets """ """ Login errors packets
(userID packets derivates) """
def loginFailed(): def loginFailed():
return packetHelper.buildPacket(packetIDs.server_userID, [[-1, dataTypes.SINT32]]) return packetHelper.buildPacket(packetIDs.server_userID, [[-1, dataTypes.sInt32]])
def forceUpdate(): def forceUpdate():
return packetHelper.buildPacket(packetIDs.server_userID, [[-2, dataTypes.SINT32]]) return packetHelper.buildPacket(packetIDs.server_userID, [[-2, dataTypes.sInt32]])
def loginBanned(): def loginBanned():
packets = packetHelper.buildPacket(packetIDs.server_userID, [[-1, dataTypes.SINT32]]) packets = packetHelper.buildPacket(packetIDs.server_userID, [[-1, dataTypes.sInt32]])
packets += notification("You are banned. You can appeal after one month since your ban by sending an email to {} from the email address you've used to sign up.".format(glob.conf.extra["pep.py"]["support-email"])) packets += notification("You are banned. You can ask to get unbanned after 1 month since your ban by contacting support@ripple.moe")
return packets
def loginLocked():
packets = packetHelper.buildPacket(packetIDs.server_userID, [[-1, dataTypes.SINT32]])
packets += notification("Your account is locked. You can't log in, but your profile and scores are still visible from the website. If you want to unlock your account, send an email to {} from the email address you've used to sign up.".format(glob.conf.extra["pep.py"]["support-email"]))
return packets return packets
#return packetHelper.buildPacket(packetIDs.server_userID, [[-3, dataTypes.sInt32]])
def loginError(): def loginError():
return packetHelper.buildPacket(packetIDs.server_userID, [[-5, dataTypes.SINT32]]) return packetHelper.buildPacket(packetIDs.server_userID, [[-5, dataTypes.sInt32]])
def needSupporter(): def needSupporter():
return packetHelper.buildPacket(packetIDs.server_userID, [[-6, dataTypes.SINT32]]) return packetHelper.buildPacket(packetIDs.server_userID, [[-6, dataTypes.sInt32]])
def needVerification(): def needVerification():
return packetHelper.buildPacket(packetIDs.server_userID, [[-8, dataTypes.SINT32]]) return packetHelper.buildPacket(packetIDs.server_userID, [[-8, dataTypes.sInt32]])
""" Login packets """ """ Login packets """
def userID(uid): def userID(uid):
return packetHelper.buildPacket(packetIDs.server_userID, [[uid, dataTypes.SINT32]]) return packetHelper.buildPacket(packetIDs.server_userID, [[uid, dataTypes.sInt32]])
def silenceEndTime(seconds): def silenceEndTime(seconds):
return packetHelper.buildPacket(packetIDs.server_silenceEnd, [[seconds, dataTypes.UINT32]]) return packetHelper.buildPacket(packetIDs.server_silenceEnd, [[seconds, dataTypes.uInt32]])
def protocolVersion(version = 19): def protocolVersion(version = 19):
return packetHelper.buildPacket(packetIDs.server_protocolVersion, [[version, dataTypes.UINT32]]) return packetHelper.buildPacket(packetIDs.server_protocolVersion, [[version, dataTypes.uInt32]])
def mainMenuIcon(icon): def mainMenuIcon(icon):
return packetHelper.buildPacket(packetIDs.server_mainMenuIcon, [[icon, dataTypes.STRING]]) return packetHelper.buildPacket(packetIDs.server_mainMenuIcon, [[icon, dataTypes.string]])
def userSupporterGMT(supporter, GMT, tournamentStaff): def userSupporterGMT(supporter, GMT):
result = 1 result = 1
if supporter: if supporter == True:
result |= userRanks.SUPPORTER result += 4
if GMT: if GMT == True:
result |= userRanks.BAT result += 2
if tournamentStaff: return packetHelper.buildPacket(packetIDs.server_supporterGMT, [[result, dataTypes.uInt32]])
result |= userRanks.TOURNAMENT_STAFF
return packetHelper.buildPacket(packetIDs.server_supporterGMT, [[result, dataTypes.UINT32]])
def friendList(userID): def friendList(userID):
friends = userUtils.getFriendList(userID) friendsData = []
return packetHelper.buildPacket(packetIDs.server_friendsList, [[friends, dataTypes.INT_LIST]])
# Get friend IDs from db
friends = userHelper.getFriendList(userID)
# Friends number
friendsData.append([len(friends), dataTypes.uInt16])
# Add all friend user IDs to friendsData
for i in friends:
friendsData.append([i, dataTypes.sInt32])
return packetHelper.buildPacket(packetIDs.server_friendsList, friendsData)
def onlineUsers(): def onlineUsers():
userIDs = [] userIDs = []
@ -67,25 +71,27 @@ def onlineUsers():
# Create list with all connected (and not restricted) users # Create list with all connected (and not restricted) users
for _, value in users.items(): for _, value in users.items():
if not value.restricted: if value.restricted == False:
userIDs.append(value.userID) userIDs.append(value.userID)
return packetHelper.buildPacket(packetIDs.server_userPresenceBundle, [[userIDs, dataTypes.INT_LIST]]) return packetHelper.buildPacket(packetIDs.server_userPresenceBundle, [[userIDs, dataTypes.intList]])
""" Users packets """ """ Users packets """
def userLogout(userID): def userLogout(userID):
return packetHelper.buildPacket(packetIDs.server_userLogout, [[userID, dataTypes.SINT32], [0, dataTypes.BYTE]]) return packetHelper.buildPacket(packetIDs.server_userLogout, [[userID, dataTypes.sInt32], [0, dataTypes.byte]])
def userPanel(userID, force = False): def userPanel(userID, force = False):
# Connected and restricted check # Connected and restricted check
userToken = glob.tokens.getTokenFromUserID(userID) userToken = glob.tokens.getTokenFromUserID(userID)
if userToken is None or ((userToken.restricted) and not force): if userToken == None:
return bytes()
if userToken.restricted == True and force == False:
return bytes() return bytes()
# Get user data # Get user data
username = userToken.username username = userToken.username
timezone = 24+userToken.timeOffset timezone = 24+userToken.timeOffset # TODO: Timezone
country = userToken.country country = userToken.country
gameRank = userToken.gameRank gameRank = userToken.gameRank
latitude = userToken.getLatitude() latitude = userToken.getLatitude()
@ -93,144 +99,134 @@ def userPanel(userID, force = False):
# Get username color according to rank # Get username color according to rank
# Only admins and normal users are currently supported # Only admins and normal users are currently supported
userRank = 0 if username == "FokaBot":
if username == glob.BOT_NAME: userRank = userRanks.MOD
userRank |= userRanks.MOD elif userHelper.isInPrivilegeGroup(userID, "community manager") == True:
elif userUtils.isInPrivilegeGroup(userID, "developer"): userRank = userRanks.MOD
userRank |= userRanks.ADMIN elif userHelper.isInPrivilegeGroup(userID, "developer") == True:
elif userUtils.isInPrivilegeGroup(userID, "chat mod"): userRank = userRanks.ADMIN
userRank |= userRanks.MOD elif userHelper.isInPrivilegeGroup(userID, "donor") == True:
elif (userToken.privileges & privileges.USER_DONOR) > 0: userRank = userRanks.SUPPORTER
userRank |= userRanks.SUPPORTER
else: else:
userRank |= userRanks.NORMAL userRank = userRanks.NORMAL
return packetHelper.buildPacket(packetIDs.server_userPanel, return packetHelper.buildPacket(packetIDs.server_userPanel,
[ [
[userID, dataTypes.SINT32], [userID, dataTypes.sInt32],
[username, dataTypes.STRING], [username, dataTypes.string],
[timezone, dataTypes.BYTE], [timezone, dataTypes.byte],
[country, dataTypes.BYTE], [country, dataTypes.byte],
[userRank, dataTypes.BYTE], [userRank, dataTypes.byte],
[longitude, dataTypes.FFLOAT], [longitude, dataTypes.ffloat],
[latitude, dataTypes.FFLOAT], [latitude, dataTypes.ffloat],
[gameRank, dataTypes.UINT32] [gameRank, dataTypes.uInt32]
]) ])
def userStats(userID, force = False): def userStats(userID, force = False):
# Get userID's token from tokens list # Get userID's token from tokens list
userToken = glob.tokens.getTokenFromUserID(userID) userToken = glob.tokens.getTokenFromUserID(userID)
if userToken is None or ((userToken.restricted or userToken.irc or userToken.tournament) and not force): if userToken == None:
return bytes()
if (userToken.restricted == True or userToken.irc == True) and force == False:
return bytes() return bytes()
return packetHelper.buildPacket(packetIDs.server_userStats, return packetHelper.buildPacket(packetIDs.server_userStats,
[ [
[userID, dataTypes.UINT32], [userID, dataTypes.uInt32],
[userToken.actionID, dataTypes.BYTE], [userToken.actionID, dataTypes.byte],
[userToken.actionText, dataTypes.STRING], [userToken.actionText, dataTypes.string],
[userToken.actionMd5, dataTypes.STRING], [userToken.actionMd5, dataTypes.string],
[userToken.actionMods, dataTypes.SINT32], [userToken.actionMods, dataTypes.sInt32],
[userToken.gameMode, dataTypes.BYTE], [userToken.gameMode, dataTypes.byte],
[userToken.beatmapID, dataTypes.SINT32], [0, dataTypes.sInt32],
[userToken.rankedScore, dataTypes.UINT64], [userToken.rankedScore, dataTypes.uInt64],
[userToken.accuracy, dataTypes.FFLOAT], [userToken.accuracy, dataTypes.ffloat],
[userToken.playcount, dataTypes.UINT32], [userToken.playcount, dataTypes.uInt32],
[userToken.totalScore, dataTypes.UINT64], [userToken.totalScore, dataTypes.uInt64],
[userToken.gameRank, dataTypes.UINT32], [userToken.gameRank, dataTypes.uInt32],
[userToken.pp if 65535 >= userToken.pp > 0 else 0, dataTypes.UINT16] [userToken.pp, dataTypes.uInt16]
]) ])
""" Chat packets """ """ Chat packets """
def sendMessage(fro, to, message): def sendMessage(fro, to, message):
return packetHelper.buildPacket(packetIDs.server_sendMessage, [ return packetHelper.buildPacket(packetIDs.server_sendMessage, [[fro, dataTypes.string], [message, dataTypes.string], [to, dataTypes.string], [userHelper.getID(fro), dataTypes.sInt32]])
[fro, dataTypes.STRING],
[message, dataTypes.STRING],
[to, dataTypes.STRING],
[userUtils.getID(fro), dataTypes.SINT32]
])
def channelJoinSuccess(userID, chan): def channelJoinSuccess(userID, chan):
return packetHelper.buildPacket(packetIDs.server_channelJoinSuccess, [[chan, dataTypes.STRING]]) return packetHelper.buildPacket(packetIDs.server_channelJoinSuccess, [[chan, dataTypes.string]])
def channelInfo(chan): def channelInfo(chan):
if chan not in glob.channels.channels:
return bytes()
channel = glob.channels.channels[chan] channel = glob.channels.channels[chan]
return packetHelper.buildPacket(packetIDs.server_channelInfo, [ return packetHelper.buildPacket(packetIDs.server_channelInfo, [[chan, dataTypes.string], [channel.description, dataTypes.string], [channel.getConnectedUsersCount(), dataTypes.uInt16]])
[channel.name, dataTypes.STRING],
[channel.description, dataTypes.STRING],
[len(glob.streams.streams["chat/{}".format(chan)].clients), dataTypes.UINT16]
])
def channelInfoEnd(): def channelInfoEnd():
return packetHelper.buildPacket(packetIDs.server_channelInfoEnd, [[0, dataTypes.UINT32]]) return packetHelper.buildPacket(packetIDs.server_channelInfoEnd, [[0, dataTypes.uInt32]])
def channelKicked(chan): def channelKicked(chan):
return packetHelper.buildPacket(packetIDs.server_channelKicked, [[chan, dataTypes.STRING]]) return packetHelper.buildPacket(packetIDs.server_channelKicked, [[chan, dataTypes.string]])
def userSilenced(userID): def userSilenced(userID):
return packetHelper.buildPacket(packetIDs.server_userSilenced, [[userID, dataTypes.UINT32]]) return packetHelper.buildPacket(packetIDs.server_userSilenced, [[userID, dataTypes.uInt32]])
""" Spectator packets """ """ Spectator packets """
def addSpectator(userID): def addSpectator(userID):
return packetHelper.buildPacket(packetIDs.server_spectatorJoined, [[userID, dataTypes.SINT32]]) return packetHelper.buildPacket(packetIDs.server_spectatorJoined, [[userID, dataTypes.sInt32]])
def removeSpectator(userID): def removeSpectator(userID):
return packetHelper.buildPacket(packetIDs.server_spectatorLeft, [[userID, dataTypes.SINT32]]) return packetHelper.buildPacket(packetIDs.server_spectatorLeft, [[userID, dataTypes.sInt32]])
def spectatorFrames(data): def spectatorFrames(data):
return packetHelper.buildPacket(packetIDs.server_spectateFrames, [[data, dataTypes.BBYTES]]) return packetHelper.buildPacket(packetIDs.server_spectateFrames, [[data, dataTypes.bbytes]])
def noSongSpectator(userID): def noSongSpectator(userID):
return packetHelper.buildPacket(packetIDs.server_spectatorCantSpectate, [[userID, dataTypes.SINT32]]) return packetHelper.buildPacket(packetIDs.server_spectatorCantSpectate, [[userID, dataTypes.sInt32]])
def fellowSpectatorJoined(userID): def fellowSpectatorJoined(userID):
return packetHelper.buildPacket(packetIDs.server_fellowSpectatorJoined, [[userID, dataTypes.SINT32]]) return packetHelper.buildPacket(packetIDs.server_fellowSpectatorJoined, [[userID, dataTypes.sInt32]])
def fellowSpectatorLeft(userID): def fellowSpectatorLeft(userID):
return packetHelper.buildPacket(packetIDs.server_fellowSpectatorLeft, [[userID, dataTypes.SINT32]]) return packetHelper.buildPacket(packetIDs.server_fellowSpectatorLeft, [[userID, dataTypes.sInt32]])
""" Multiplayer Packets """ """ Multiplayer Packets """
def createMatch(matchID): def createMatch(matchID):
# Make sure the match exists # Make sure the match exists
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
return bytes() return None
# Get match binary data and build packet # Get match binary data and build packet
match = glob.matches.matches[matchID] match = glob.matches.matches[matchID]
matchData = match.getMatchData(censored=True) return packetHelper.buildPacket(packetIDs.server_newMatch, match.getMatchData())
return packetHelper.buildPacket(packetIDs.server_newMatch, matchData)
# TODO: Add match object argument to save some CPU
def updateMatch(matchID, censored = False): def updateMatch(matchID):
# Make sure the match exists # Make sure the match exists
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
return bytes() return None
# Get match binary data and build packet # Get match binary data and build packet
match = glob.matches.matches[matchID] match = glob.matches.matches[matchID]
return packetHelper.buildPacket(packetIDs.server_updateMatch, match.getMatchData(censored=censored)) return packetHelper.buildPacket(packetIDs.server_updateMatch, match.getMatchData())
def matchStart(matchID): def matchStart(matchID):
# Make sure the match exists # Make sure the match exists
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
return bytes() return None
# Get match binary data and build packet # Get match binary data and build packet
match = glob.matches.matches[matchID] match = glob.matches.matches[matchID]
return packetHelper.buildPacket(packetIDs.server_matchStart, match.getMatchData()) return packetHelper.buildPacket(packetIDs.server_matchStart, match.getMatchData())
def disposeMatch(matchID): def disposeMatch(matchID):
return packetHelper.buildPacket(packetIDs.server_disposeMatch, [[matchID, dataTypes.UINT32]]) return packetHelper.buildPacket(packetIDs.server_disposeMatch, [[matchID, dataTypes.uInt16]])
def matchJoinSuccess(matchID): def matchJoinSuccess(matchID):
# Make sure the match exists # Make sure the match exists
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
return bytes() return None
# Get match binary data and build packet # Get match binary data and build packet
match = glob.matches.matches[matchID] match = glob.matches.matches[matchID]
@ -241,41 +237,32 @@ def matchJoinFail():
return packetHelper.buildPacket(packetIDs.server_matchJoinFail) return packetHelper.buildPacket(packetIDs.server_matchJoinFail)
def changeMatchPassword(newPassword): def changeMatchPassword(newPassword):
return packetHelper.buildPacket(packetIDs.server_matchChangePassword, [[newPassword, dataTypes.STRING]]) return packetHelper.buildPacket(packetIDs.server_matchChangePassword, [[newPassword, dataTypes.string]])
def allPlayersLoaded(): def allPlayersLoaded():
return packetHelper.buildPacket(packetIDs.server_matchAllPlayersLoaded) return packetHelper.buildPacket(packetIDs.server_matchAllPlayersLoaded)
def playerSkipped(userID): def playerSkipped(userID):
return packetHelper.buildPacket(packetIDs.server_matchPlayerSkipped, [[userID, dataTypes.SINT32]]) return packetHelper.buildPacket(packetIDs.server_matchPlayerSkipped, [[userID, dataTypes.sInt32]])
def allPlayersSkipped(): def allPlayersSkipped():
return packetHelper.buildPacket(packetIDs.server_matchSkip) return packetHelper.buildPacket(packetIDs.server_matchSkip)
def matchFrames(slotID, data): def matchFrames(slotID, data):
return packetHelper.buildPacket(packetIDs.server_matchScoreUpdate, [[data[7:11], dataTypes.BBYTES], [slotID, dataTypes.BYTE], [data[12:], dataTypes.BBYTES]]) return packetHelper.buildPacket(packetIDs.server_matchScoreUpdate, [[data[7:11], dataTypes.bbytes], [slotID, dataTypes.byte], [data[12:], dataTypes.bbytes]])
def matchComplete(): def matchComplete():
return packetHelper.buildPacket(packetIDs.server_matchComplete) return packetHelper.buildPacket(packetIDs.server_matchComplete)
def playerFailed(slotID): def playerFailed(slotID):
return packetHelper.buildPacket(packetIDs.server_matchPlayerFailed, [[slotID, dataTypes.UINT32]]) return packetHelper.buildPacket(packetIDs.server_matchPlayerFailed, [[slotID, dataTypes.uInt32]])
def matchTransferHost(): def matchTransferHost():
return packetHelper.buildPacket(packetIDs.server_matchTransferHost) return packetHelper.buildPacket(packetIDs.server_matchTransferHost)
def matchAbort():
return packetHelper.buildPacket(packetIDs.server_matchAbort)
def switchServer(address):
return packetHelper.buildPacket(packetIDs.server_switchServer, [[address, dataTypes.STRING]])
""" Other packets """ """ Other packets """
def notification(message): def notification(message):
return packetHelper.buildPacket(packetIDs.server_notification, [[message, dataTypes.STRING]]) return packetHelper.buildPacket(packetIDs.server_notification, [[message, dataTypes.string]])
def banchoRestart(msUntilReconnection): def banchoRestart(msUntilReconnection):
return packetHelper.buildPacket(packetIDs.server_restart, [[msUntilReconnection, dataTypes.UINT32]]) return packetHelper.buildPacket(packetIDs.server_restart, [[msUntilReconnection, dataTypes.uInt32]])
def rtx(message):
return packetHelper.buildPacket(0x69, [[message, dataTypes.STRING]])

View File

@ -1,8 +1,8 @@
FREE = 1 free = 1
LOCKED = 2 locked = 2
NOT_READY = 4 notReady = 4
READY = 8 ready = 8
NO_MAP = 16 noMap = 16
PLAYING = 32 playing = 32
OCCUPIED = 124 occupied = 124
PLAYING_QUIT = 128 playingQuit = 128

View File

@ -1,8 +1,8 @@
"""Bancho user ranks"""
NORMAL = 0 NORMAL = 0
PLAYER = 1 PLAYER = 1
BAT = 2
SUPPORTER = 4 SUPPORTER = 4
MOD = 6 MOD = 6
PEPPY = 8 PEPPY = 8
ADMIN = 16 ADMIN = 16
TOURNAMENT_STAFF = 32 TOURNAMENTSTAFF = 32

View File

@ -1,17 +1,19 @@
from common.log import logUtils as log
from constants import exceptions
from constants import serverPackets
from objects import glob from objects import glob
from constants import serverPackets
from constants import exceptions
from helpers import logHelper as log
def handle(userToken, packetData):
# get usertoken data
userID = userToken.userID
def handle(userToken, _):
try: try:
# We don't have the beatmap, we can't spectate # We don't have the beatmap, we can't spectate
if userToken.spectating not in glob.tokens.tokens: target = userToken.spectating
raise exceptions.tokenNotFoundException() targetToken = glob.tokens.getTokenFromUserID(target)
# Send the packet to host # Send the packet to host
glob.tokens.tokens[userToken.spectating].enqueue(serverPackets.noSongSpectator(userToken.userID)) targetToken.enqueue(serverPackets.noSongSpectator(userID))
except exceptions.tokenNotFoundException: except exceptions.tokenNotFoundException:
# Stop spectating if token not found # Stop spectating if token not found
log.warning("Spectator can't spectate: token not found") log.warning("Spectator can't spectate: token not found")

View File

@ -1,7 +1,10 @@
from common.log import logUtils as log from objects import glob
from constants import clientPackets from constants import clientPackets
from constants import serverPackets from constants import serverPackets
from objects import glob from helpers import userHelper
from helpers import logHelper as log
from constants import actions
from helpers import chatHelper as chat
def handle(userToken, packetData): def handle(userToken, packetData):
# Get usertoken data # Get usertoken data
@ -9,55 +12,55 @@ def handle(userToken, packetData):
username = userToken.username username = userToken.username
# Make sure we are not banned # Make sure we are not banned
#if userUtils.isBanned(userID): if userHelper.isBanned(userID) == True:
# userToken.enqueue(serverPackets.loginBanned()) userToken.enqueue(serverPackets.loginBanned())
# return return
# Send restricted message if needed # Send restricted message if needed
#if userToken.restricted: if userToken.restricted == False:
# userToken.checkRestricted(True) if userHelper.isRestricted(userID) == True:
userToken.setRestricted()
# Change action packet # Change action packet
packetData = clientPackets.userActionChange(packetData) packetData = clientPackets.userActionChange(packetData)
# If we are not in spectate status but we're spectating someone, stop spectating # Update cached stats if our pp changedm if we've just submitted a score or we've changed gameMode
''' if (userToken.actionID == actions.playing or userToken.actionID == actions.multiplaying) or (userToken.pp != userHelper.getPP(userID, userToken.gameMode)) or (userToken.gameMode != packetData["gameMode"]):
if userToken.spectating != 0 and userToken.actionID != actions.WATCHING and userToken.actionID != actions.IDLE and userToken.actionID != actions.AFK: log.debug("!!!! UPDATING CACHED STATS !!!!")
userToken.stopSpectating() # Always update game mode, or we'll cache stats from the wrong game mode if we've changed it
# If we are not in multiplayer but we are in a match, part match
if userToken.matchID != -1 and userToken.actionID != actions.MULTIPLAYING and userToken.actionID != actions.MULTIPLAYER and userToken.actionID != actions.AFK:
userToken.partMatch()
'''
# Update cached stats if our pp changed if we've just submitted a score or we've changed gameMode
#if (userToken.actionID == actions.PLAYING or userToken.actionID == actions.MULTIPLAYING) or (userToken.pp != userUtils.getPP(userID, userToken.gameMode)) or (userToken.gameMode != packetData["gameMode"]):
# Update cached stats if we've changed gamemode
if userToken.gameMode != packetData["gameMode"]:
userToken.gameMode = packetData["gameMode"] userToken.gameMode = packetData["gameMode"]
userToken.updateCachedStats() userToken.updateCachedStats()
# Always update action id, text, md5 and beatmapID # Always update action id, text and md5
userToken.actionID = packetData["actionID"] userToken.actionID = packetData["actionID"]
userToken.actionText = packetData["actionText"] userToken.actionText = packetData["actionText"]
userToken.actionMd5 = packetData["actionMd5"] userToken.actionMd5 = packetData["actionMd5"]
userToken.actionMods = packetData["actionMods"] userToken.actionMods = packetData["actionMods"]
userToken.beatmapID = packetData["beatmapID"]
# Enqueue our new user panel and stats to us and our spectators # Enqueue our new user panel and stats to us and our spectators
recipients = [userToken] recipients = [userID]
if len(userToken.spectators) > 0: if len(userToken.spectators) > 0:
for i in userToken.spectators: recipients += userToken.spectators
if i in glob.tokens.tokens:
recipients.append(glob.tokens.tokens[i])
for i in recipients: for i in recipients:
if i is not None: if i == userID:
# Save some loops
token = userToken
else:
token = glob.tokens.getTokenFromUserID(i)
if token != None:
# Force our own packet # Force our own packet
force = True if i == userToken else False force = True if token.userID == userID else False
i.enqueue(serverPackets.userPanel(userID, force)) token.enqueue(serverPackets.userPanel(userID, force))
i.enqueue(serverPackets.userStats(userID, force)) token.enqueue(serverPackets.userStats(userID, force))
# Send osu!direct alert if needed
# NOTE: Remove this when osu!direct will be fixed
if userToken.actionID == actions.osuDirect and userToken.osuDirectAlert == False:
userToken.osuDirectAlert = True
chat.sendMessage("FokaBot", userToken.username, "Sup! osu!direct works, but you'll need to update the switcher to have the Download button working. If you didn't update the switcher yet, please do!")
# Console output # Console output
log.info("{} changed action: {} [{}][{}][{}]".format(username, str(userToken.actionID), userToken.actionText, userToken.actionMd5, userToken.beatmapID)) log.info("{} changed action: {} [{}][{}]".format(username, str(userToken.actionID), userToken.actionText, userToken.actionMd5))

View File

@ -1,8 +1,7 @@
from common.constants import mods from objects import glob
from constants import clientPackets from constants import clientPackets
from constants import matchModModes from constants import matchModModes
from objects import glob from constants import mods
def handle(userToken, packetData): def handle(userToken, packetData):
# Get token data # Get token data
@ -15,29 +14,30 @@ def handle(userToken, packetData):
matchID = userToken.matchID matchID = userToken.matchID
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
return return
match = glob.matches.matches[matchID]
# Set slot or match mods according to modType # Set slot or match mods according to modType
with glob.matches.matches[matchID] as match: if match.matchModMode == matchModModes.freeMod:
if match.matchModMode == matchModModes.FREE_MOD: # 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.changeMods(mods.DOUBLETIME)
# Nightcore
if (packetData["mods"] & mods.NIGHTCORE) > 0:
match.changeMods(match.mods + mods.NIGHTCORE)
elif (packetData["mods"] & mods.HALFTIME) > 0:
match.changeMods(mods.HALFTIME)
else:
# No DT/HT, set global mods to 0 (we are in freemod mode)
match.changeMods(0)
# Set slot mods # Host can set global DT/HT
slotID = match.getUserSlotID(userID) if userID == match.hostUserID:
if slotID is not None: # If host has selected DT/HT and Freemod is enabled, set DT/HT as match mod
match.setSlotMods(slotID, packetData["mods"]) if (packetData["mods"] & mods.DoubleTime) > 0:
else: match.changeMatchMods(mods.DoubleTime)
# Not freemod, set match mods # Nighcore
match.changeMods(packetData["mods"]) 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

@ -10,10 +10,8 @@ def handle(userToken, packetData):
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
return return
with glob.matches.matches[matchID] as match: # Get our match
# Host check match = glob.matches.matches[matchID]
if userToken.userID != match.hostUserID:
return
# Update match password # Update match password
match.changePassword(packetData["matchPassword"]) match.changePassword(packetData["matchPassword"])

View File

@ -1,14 +1,13 @@
import random from objects import glob
from common import generalUtils
from common.log import logUtils as log
from constants import clientPackets from constants import clientPackets
from constants import matchModModes from constants import matchModModes
from helpers import consoleHelper
from constants import bcolors
import random
from constants import matchTeamTypes from constants import matchTeamTypes
from constants import matchTeams from constants import matchTeams
from constants import slotStatuses from constants import slotStatuses
from objects import glob from helpers import logHelper as log
def handle(userToken, packetData): def handle(userToken, packetData):
# Read new settings # Read new settings
@ -16,89 +15,96 @@ def handle(userToken, packetData):
# Get match ID # Get match ID
matchID = userToken.matchID matchID = userToken.matchID
# Make sure the match exists # Make sure the match exists
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
return return
# Host check # Get match object
with glob.matches.matches[matchID] as match: match = glob.matches.matches[matchID]
if userToken.userID != match.hostUserID:
return
# Some dank memes easter egg # Some dank memes easter egg
memeTitles = [ memeTitles = [
"RWC 2020", "RWC 2020",
"Fokabot is a duck", "Fokabot is a duck",
"Dank memes", "Dank memes",
"1337ms Ping", "1337ms Ping",
"Iscriviti a Xenotoze", "Iscriviti a Xenotoze",
"...e i marò?", "...e i marò?",
"Superman dies", "Superman dies",
"The brace is on fire", "The brace is on fire",
"print_foot()", "print_foot()",
"#FREEZEBARKEZ", "#FREEZEBARKEZ",
"Ripple devs are actually cats", "Ripple devs are actually cats",
"Thank Mr Shaural", "Thank Mr Shaural",
"NEVER GIVE UP", "NEVER GIVE UP",
"T I E D W I T H U N I T E D", "T I E D W I T H U N I T E D",
"HIGHEST HDHR LOBBY OF ALL TIME", "HIGHEST HDHR LOBBY OF ALL TIME",
"This is gasoline and I set myself on fire", "This is gasoline and I set myself on fire",
"Everyone is cheating apparently", "Everyone is cheating apparently",
"Kurwa mac", "Kurwa mac",
"TATOE", "TATOE",
"This is not your drama landfill.", "This is not your drama landfill.",
"I like cheese", "I like cheese",
"NYO IS NOT A CAT HE IS A DO(N)G", "NYO IS NOT A CAT HE IS A DO(N)G",
"Datingu startuato" "Datingu startuato"
] ]
# Set match name # Set match name
match.matchName = packetData["matchName"] if packetData["matchName"] != "meme" else random.choice(memeTitles) match.matchName = packetData["matchName"] if packetData["matchName"] != "meme" else random.choice(memeTitles)
# Update match settings # Update match settings
match.inProgress = packetData["inProgress"] match.inProgress = packetData["inProgress"]
if packetData["matchPassword"] != "": match.matchPassword = packetData["matchPassword"]
match.matchPassword = generalUtils.stringMd5(packetData["matchPassword"]) match.beatmapName = packetData["beatmapName"]
else: match.beatmapID = packetData["beatmapID"]
match.matchPassword = "" match.hostUserID = packetData["hostUserID"]
match.beatmapName = packetData["beatmapName"] match.gameMode = packetData["gameMode"]
match.beatmapID = packetData["beatmapID"]
match.hostUserID = packetData["hostUserID"]
match.gameMode = packetData["gameMode"]
oldBeatmapMD5 = match.beatmapMD5 oldBeatmapMD5 = match.beatmapMD5
oldMods = match.mods oldMods = match.mods
oldMatchTeamType = match.matchTeamType
match.mods = packetData["mods"] match.mods = packetData["mods"]
match.beatmapMD5 = packetData["beatmapMD5"] match.beatmapMD5 = packetData["beatmapMD5"]
match.matchScoringType = packetData["scoringType"] match.matchScoringType = packetData["scoringType"]
match.matchTeamType = packetData["teamType"] match.matchTeamType = packetData["teamType"]
match.matchModMode = packetData["freeMods"] match.matchModMode = packetData["freeMods"]
# Reset ready if needed # Reset ready if needed
if oldMods != match.mods or oldBeatmapMD5 != match.beatmapMD5: if oldMods != match.mods or oldBeatmapMD5 != match.beatmapMD5:
match.resetReady() for i in range(0,16):
if match.slots[i]["status"] == slotStatuses.ready:
match.slots[i]["status"] = slotStatuses.notReady
# Reset mods if needed # Reset mods if needed
if match.matchModMode == matchModModes.NORMAL: if match.matchModMode == matchModModes.normal:
# Reset slot mods if not freeMods # Reset slot mods if not freeMods
match.resetMods() for i in range(0,16):
else: match.slots[i]["mods"] = 0
# Reset match mods if freemod else:
match.mods = 0 # Reset match mods if freemod
match.mods = 0
# Initialize teams if team type changed # Set/reset teams
if match.matchTeamType != oldMatchTeamType: if match.matchTeamType == matchTeamTypes.teamVs or match.matchTeamType == matchTeamTypes.tagTeamVs:
match.initializeTeams() # 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 # Force no freemods if tag coop
if match.matchTeamType == matchTeamTypes.TAG_COOP or match.matchTeamType == matchTeamTypes.TAG_TEAM_VS: if match.matchTeamType == matchTeamTypes.tagCoop or match.matchTeamType == matchTeamTypes.tagTeamVs:
match.matchModMode = matchModModes.NORMAL match.matchModMode = matchModModes.normal
# Send updated settings # Send updated settings
match.sendUpdates() match.sendUpdate()
# Console output # Console output
log.info("MPROOM{}: Updated room settings".format(match.matchID)) log.info("MPROOM{}: Updated room settings".format(match.matchID))
#consoleHelper.printColored("> MPROOM{}: DEBUG: Host is {}".format(match.matchID, match.hostUserID), bcolors.PINK)

View File

@ -1,13 +1,18 @@
from constants import clientPackets from constants import clientPackets
from objects import glob from objects import glob
from helpers import consoleHelper
from constants import bcolors
def handle(userToken, packetData): def handle(userToken, packetData):
# Get usertoken data # Get usertoken data
userID = userToken.userID userID = userToken.userID
username = userToken.username
# Read packet data # Read packet data
packetData = clientPackets.changeSlot(packetData) packetData = clientPackets.changeSlot(packetData)
with glob.matches.matches[userToken.matchID] as match: # Get match
# Change slot match = glob.matches.matches[userToken.matchID]
match.userChangeSlot(userID, packetData["slotID"])
# Change slot
match.userChangeSlot(userID, packetData["slotID"])

View File

@ -1,3 +1,6 @@
"""
Event called when someone parts a channel
"""
from constants import clientPackets from constants import clientPackets
from helpers import chatHelper as chat from helpers import chatHelper as chat

View File

@ -1,8 +1,9 @@
from common.log import logUtils as log from constants import serverPackets
from constants import clientPackets, serverPackets from constants import clientPackets
from constants import exceptions
from objects import glob from objects import glob
from events import joinMatchEvent
from constants import exceptions
from helpers import logHelper as log
def handle(userToken, packetData): def handle(userToken, packetData):
try: try:
@ -12,32 +13,31 @@ def handle(userToken, packetData):
# Read packet data # Read packet data
packetData = clientPackets.createMatch(packetData) packetData = clientPackets.createMatch(packetData)
# Make sure the name is valid
matchName = packetData["matchName"].strip()
if not matchName:
raise exceptions.matchCreateError()
# Create a match object # Create a match object
# TODO: Player number check (Dirty hack below) # TODO: Player number check
matchID = glob.matches.createMatch(matchName, packetData["matchPassword"].strip(), packetData["beatmapID"], packetData["beatmapName"], packetData["beatmapMD5"], packetData["gameMode"], userID) matchID = glob.matches.createMatch(packetData["matchName"], packetData["matchPassword"], packetData["beatmapID"], packetData["beatmapName"], packetData["beatmapMD5"], packetData["gameMode"], userID)
# Make sure the match has been created # Make sure the match has been created
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
raise exceptions.matchCreateError() raise exceptions.matchCreateError
with glob.matches.matches[matchID] as match: # Get match object
# Join that match match = glob.matches.matches[matchID]
userToken.joinMatch(matchID)
# Disable slots (Dirty) # Join that match
for i in range(0,16): joinMatchEvent.joinMatch(userToken, matchID, packetData["matchPassword"])
if match.slots[i].status is not 4:
match.slots[i].status = packetData["slot{}Status".format(i)]
# Give host to match creator # Give host to match creator
match.setHost(userID) match.setHost(userID)
match.sendUpdates()
match.changePassword(packetData["matchPassword"]) # 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
log.info("MPROOM{}: Room created!".format(matchID))
except exceptions.matchCreateError: except exceptions.matchCreateError:
log.error("Error while creating match!") log.error("Error while creating match!")
userToken.enqueue(serverPackets.matchJoinFail())

View File

@ -1,12 +1,11 @@
from common.log import logUtils as log from helpers import userHelper
from common.ripple import userUtils
from constants import clientPackets from constants import clientPackets
from helpers import logHelper as log
def handle(userToken, packetData): def handle(userToken, packetData):
# Friend add packet # Friend add packet
packetData = clientPackets.addRemoveFriend(packetData) packetData = clientPackets.addRemoveFriend(packetData)
userUtils.addFriend(userToken.userID, packetData["friendID"]) userHelper.addFriend(userToken.userID, packetData["friendID"])
# Console output # Console output
log.info("{} have added {} to their friends".format(userToken.username, str(packetData["friendID"]))) log.info("{} have added {} to their friends".format(userToken.username, str(packetData["friendID"])))

View File

@ -1,12 +1,11 @@
from common.log import logUtils as log from helpers import userHelper
from common.ripple import userUtils
from constants import clientPackets from constants import clientPackets
from helpers import logHelper as log
def handle(userToken, packetData): def handle(userToken, packetData):
# Friend remove packet # Friend remove packet
packetData = clientPackets.addRemoveFriend(packetData) packetData = clientPackets.addRemoveFriend(packetData)
userUtils.removeFriend(userToken.userID, packetData["friendID"]) userHelper.removeFriend(userToken.userID, packetData["friendID"])
# Console output # Console output
log.info("{} have removed {} from their friends".format(userToken.username, str(packetData["friendID"]))) log.info("{} have removed {} from their friends".format(userToken.username, str(packetData["friendID"])))

View File

@ -1,14 +1,14 @@
from common.log import logUtils as log
from constants import serverPackets from constants import serverPackets
from objects import glob from objects import glob
from helpers import logHelper as log
def handle(userToken, _): def handle(userToken, _):
# Get userToken data # Get userToken data
username = userToken.username username = userToken.username
userID = userToken.userID
# Add user to users in lobby # Add user to users in lobby
userToken.joinStream("lobby") glob.matches.lobbyUserJoin(userID)
# Send matches data # Send matches data
for key, _ in glob.matches.matches.items(): for key, _ in glob.matches.matches.items():

View File

@ -1,33 +1,57 @@
from common.log import logUtils as log
from constants import clientPackets from constants import clientPackets
from constants import exceptions
from constants import serverPackets from constants import serverPackets
from objects import glob from objects import glob
from constants import exceptions
from helpers import logHelper as log
from helpers import chatHelper as chat
def handle(userToken, packetData): def handle(userToken, packetData):
# read packet data # read packet data
packetData = clientPackets.joinMatch(packetData) packetData = clientPackets.joinMatch(packetData)
matchID = packetData["matchID"]
password = packetData["password"]
# Get match from ID # Get match from ID
joinMatch(userToken, packetData["matchID"], packetData["password"])
def joinMatch(userToken, matchID, password):
try: try:
# TODO: leave other matches
# TODO: Stop spectating
# get usertoken data
userID = userToken.userID
# Make sure the match exists # Make sure the match exists
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
return raise exceptions.matchNotFoundException
# Hash password if needed # Match exists, get object
# if password != "": match = glob.matches.matches[matchID]
# password = generalUtils.stringMd5(password)
# Check password # Check password
with glob.matches.matches[matchID] as match: # TODO: Admins can enter every match
if match.matchPassword != "" and match.matchPassword != password: if match.matchPassword != "":
raise exceptions.matchWrongPasswordException() if match.matchPassword != password:
raise exceptions.matchWrongPasswordException
# Password is correct, join match # Password is correct, join match
userToken.joinMatch(matchID) result = match.userJoin(userID)
# Check if we've joined the match successfully
if result == False:
raise exceptions.matchJoinErrorException
# Match joined, set matchID for usertoken
userToken.joinMatch(matchID)
# Send packets
userToken.enqueue(serverPackets.matchJoinSuccess(matchID))
chat.joinChannel(token=userToken, channel="#multi_{}".format(matchID))
except exceptions.matchNotFoundException:
userToken.enqueue(serverPackets.matchJoinFail())
log.warning("{} has tried to join a mp room, but it doesn't exist".format(userToken.username))
except exceptions.matchWrongPasswordException: except exceptions.matchWrongPasswordException:
userToken.enqueue(serverPackets.matchJoinFail()) userToken.enqueue(serverPackets.matchJoinFail())
log.warning("{} has tried to join a mp room, but he typed the wrong password".format(userToken.username)) log.warning("{} has tried to join a mp room, but he typed the wrong password".format(userToken.username))
except exceptions.matchJoinErrorException:
userToken.enqueue(serverPackets.matchJoinFail())
log.warning("{} has tried to join a mp room, but an error has occured".format(userToken.username))

View File

@ -1,21 +1,23 @@
import sys from helpers import userHelper
import time
import traceback
from common.constants import privileges
from common.log import logUtils as log
from common.ripple import userUtils
from constants import exceptions
from constants import serverPackets from constants import serverPackets
from helpers import chatHelper as chat from constants import exceptions
from helpers import countryHelper
from helpers import locationHelper
from objects import glob from objects import glob
from helpers import consoleHelper
from constants import bcolors
from helpers import locationHelper
from helpers import countryHelper
import time
from helpers import generalFunctions
import sys
import traceback
from helpers import requestHelper
from helpers import discordBotHelper
from helpers import logHelper as log
from helpers import chatHelper as chat
from constants import privileges
def handle(tornadoRequest): def handle(tornadoRequest):
# Data to return # Data to return
responseToken = None
responseTokenString = "ayy" responseTokenString = "ayy"
responseData = bytes() responseData = bytes()
@ -30,9 +32,12 @@ def handle(tornadoRequest):
# 2:-3 thing is because requestData has some escape stuff that we don't need # 2:-3 thing is because requestData has some escape stuff that we don't need
loginData = str(tornadoRequest.request.body)[2:-3].split("\\n") loginData = str(tornadoRequest.request.body)[2:-3].split("\\n")
try: try:
# If true, print error to console
err = False
# Make sure loginData is valid # Make sure loginData is valid
if len(loginData) < 3: if len(loginData) < 3:
raise exceptions.invalidArgumentsException() raise exceptions.haxException()
# Get HWID, MAC address and more # Get HWID, MAC address and more
# Structure (new line = "|", already split) # Structure (new line = "|", already split)
@ -44,30 +49,29 @@ def handle(tornadoRequest):
splitData = loginData[2].split("|") splitData = loginData[2].split("|")
osuVersion = splitData[0] osuVersion = splitData[0]
timeOffset = int(splitData[1]) timeOffset = int(splitData[1])
print(str(timeOffset))
clientData = splitData[3].split(":")[:5] clientData = splitData[3].split(":")[:5]
if len(clientData) < 4: if len(clientData) < 4:
raise exceptions.forceUpdateException() raise exceptions.forceUpdateException()
# Try to get the ID from username # Try to get the ID from username
username = str(loginData[0]) username = str(loginData[0])
userID = userUtils.getID(username) userID = userHelper.getID(username)
if not userID: if userID == False:
# Invalid username # Invalid username
raise exceptions.loginFailedException() raise exceptions.loginFailedException()
if not userUtils.checkLogin(userID, loginData[1]): if userHelper.checkLogin(userID, loginData[1]) == False:
# Invalid password # Invalid password
raise exceptions.loginFailedException() raise exceptions.loginFailedException()
# Make sure we are not banned or locked # Make sure we are not banned
priv = userUtils.getPrivileges(userID) priv = userHelper.getPrivileges(userID)
if userUtils.isBanned(userID) and priv & privileges.USER_PENDING_VERIFICATION == 0: if userHelper.isBanned(userID) == True and priv & privileges.USER_PENDING_VERIFICATION == 0:
raise exceptions.loginBannedException() raise exceptions.loginBannedException()
if userUtils.isLocked(userID) and priv & privileges.USER_PENDING_VERIFICATION == 0:
raise exceptions.loginLockedException()
# 2FA check # 2FA check
if userUtils.check2FA(userID, requestIP): if userHelper.check2FA(userID, requestIP) == True:
log.warning("Need 2FA check for user {}".format(loginData[0])) log.warning("Need 2FA check for user {}".format(loginData[0]))
raise exceptions.need2FAException() raise exceptions.need2FAException()
@ -75,8 +79,8 @@ def handle(tornadoRequest):
# Verify this user (if pending activation) # Verify this user (if pending activation)
firstLogin = False firstLogin = False
if priv & privileges.USER_PENDING_VERIFICATION > 0 or not userUtils.hasVerifiedHardware(userID): if priv & privileges.USER_PENDING_VERIFICATION > 0 or userHelper.hasVerifiedHardware(userID) == False:
if userUtils.verifyUser(userID, clientData): if userHelper.verifyUser(userID, clientData) == True:
# Valid account # Valid account
log.info("Account {} verified successfully!".format(userID)) log.info("Account {} verified successfully!".format(userID))
glob.verifiedCache[str(userID)] = 1 glob.verifiedCache[str(userID)] = 1
@ -89,41 +93,27 @@ def handle(tornadoRequest):
# Save HWID in db for multiaccount detection # Save HWID in db for multiaccount detection
hwAllowed = userUtils.logHardware(userID, clientData, firstLogin) hwAllowed = userHelper.logHardware(userID, clientData, firstLogin)
# This is false only if HWID is empty # This is false only if HWID is empty
# if HWID is banned, we get restricted so there's no # if HWID is banned, we get restricted so there's no
# need to deny bancho access # need to deny bancho access
if not hwAllowed: if hwAllowed == False:
raise exceptions.haxException() raise exceptions.haxException()
# Log user IP # Log user IP
userUtils.logIP(userID, requestIP) userHelper.logIP(userID, requestIP)
# Delete old tokens for that user and generate a new one # Delete old tokens for that user and generate a new one
isTournament = "tourney" in osuVersion glob.tokens.deleteOldTokens(userID)
if not isTournament: responseToken = glob.tokens.addToken(userID, requestIP, timeOffset=timeOffset)
glob.tokens.deleteOldTokens(userID)
responseToken = glob.tokens.addToken(userID, requestIP, timeOffset=timeOffset, tournament=isTournament)
responseTokenString = responseToken.token responseTokenString = responseToken.token
# Check restricted mode (and eventually send message) # Check restricted mode (and eventually send message)
responseToken.checkRestricted() responseToken.checkRestricted()
# Send message if donor expires soon
if responseToken.privileges & privileges.USER_DONOR > 0:
expireDate = userUtils.getDonorExpire(responseToken.userID)
if expireDate-int(time.time()) <= 86400*3:
expireDays = round((expireDate-int(time.time()))/86400)
expireIn = "{} days".format(expireDays) if expireDays > 1 else "less than 24 hours"
responseToken.enqueue(serverPackets.notification("Your donor tag expires in {}! When your donor tag expires, you won't have any of the donor privileges, like yellow username, custom badge and discord custom role and username color! If you wish to keep supporting Ripple and you don't want to lose your donor privileges, you can donate again by clicking on 'Support us' on Ripple's website.".format(expireIn)))
# Deprecate telegram 2fa and send alert
if userUtils.deprecateTelegram2Fa(userID):
responseToken.enqueue(serverPackets.notification("As stated on our blog, Telegram 2FA has been deprecated on 29th June 2018. Telegram 2FA has just been disabled from your account. If you want to keep your account secure with 2FA, please enable TOTP-based 2FA from our website https://ripple.moe. Thank you for your patience."))
# Set silence end UNIX time in token # Set silence end UNIX time in token
responseToken.silenceEndTime = userUtils.getSilenceEnd(userID) responseToken.silenceEndTime = userHelper.getSilenceEnd(userID)
# Get only silence remaining seconds # Get only silence remaining seconds
silenceSeconds = responseToken.getSilenceSecondsLeft() silenceSeconds = responseToken.getSilenceSecondsLeft()
@ -131,14 +121,11 @@ def handle(tornadoRequest):
# Get supporter/GMT # Get supporter/GMT
userGMT = False userGMT = False
userSupporter = True userSupporter = True
userTournament = False if responseToken.admin == True:
if responseToken.admin:
userGMT = True userGMT = True
if responseToken.privileges & privileges.USER_TOURNAMENT_STAFF > 0:
userTournament = True
# Server restarting check # Server restarting check
if glob.restarting: if glob.restarting == True:
raise exceptions.banchoRestartingException() raise exceptions.banchoRestartingException()
# Send login notification before maintenance message # Send login notification before maintenance message
@ -146,8 +133,8 @@ def handle(tornadoRequest):
responseToken.enqueue(serverPackets.notification(glob.banchoConf.config["loginNotification"])) responseToken.enqueue(serverPackets.notification(glob.banchoConf.config["loginNotification"]))
# Maintenance check # Maintenance check
if glob.banchoConf.config["banchoMaintenance"]: if glob.banchoConf.config["banchoMaintenance"] == True:
if not userGMT: if userGMT == False:
# We are not mod/admin, delete token, send notification and logout # We are not mod/admin, delete token, send notification and logout
glob.tokens.deleteToken(responseTokenString) glob.tokens.deleteToken(responseTokenString)
raise exceptions.banchoMaintenanceException() raise exceptions.banchoMaintenanceException()
@ -159,7 +146,7 @@ def handle(tornadoRequest):
responseToken.enqueue(serverPackets.silenceEndTime(silenceSeconds)) responseToken.enqueue(serverPackets.silenceEndTime(silenceSeconds))
responseToken.enqueue(serverPackets.userID(userID)) responseToken.enqueue(serverPackets.userID(userID))
responseToken.enqueue(serverPackets.protocolVersion()) responseToken.enqueue(serverPackets.protocolVersion())
responseToken.enqueue(serverPackets.userSupporterGMT(userSupporter, userGMT, userTournament)) responseToken.enqueue(serverPackets.userSupporterGMT(userSupporter, userGMT))
responseToken.enqueue(serverPackets.userPanel(userID, True)) responseToken.enqueue(serverPackets.userPanel(userID, True))
responseToken.enqueue(serverPackets.userStats(userID, True)) responseToken.enqueue(serverPackets.userStats(userID, True))
@ -171,12 +158,12 @@ def handle(tornadoRequest):
chat.joinChannel(token=responseToken, channel="#announce") chat.joinChannel(token=responseToken, channel="#announce")
# Join admin channel if we are an admin # Join admin channel if we are an admin
if responseToken.admin: if responseToken.admin == True:
chat.joinChannel(token=responseToken, channel="#admin") chat.joinChannel(token=responseToken, channel="#admin")
# Output channels info # Output channels info
for key, value in glob.channels.channels.items(): for key, value in glob.channels.channels.items():
if value.publicRead and not value.hidden: if value.publicRead == True and value.hidden == False:
responseToken.enqueue(serverPackets.channelInfo(key)) responseToken.enqueue(serverPackets.channelInfo(key))
# Send friends list # Send friends list
@ -186,37 +173,33 @@ def handle(tornadoRequest):
if glob.banchoConf.config["menuIcon"] != "": if glob.banchoConf.config["menuIcon"] != "":
responseToken.enqueue(serverPackets.mainMenuIcon(glob.banchoConf.config["menuIcon"])) responseToken.enqueue(serverPackets.mainMenuIcon(glob.banchoConf.config["menuIcon"]))
# Send online users' panels # Send online users IDs array
with glob.tokens: responseToken.enqueue(serverPackets.onlineUsers())
for _, token in glob.tokens.tokens.items():
if not token.restricted:
responseToken.enqueue(serverPackets.userPanel(token.userID))
# Get location and country from ip.zxq.co or database # Get location and country from ip.zxq.co or database
if glob.localize: if glob.localize == True:
# Get location and country from IP # Get location and country from IP
latitude, longitude = locationHelper.getLocation(requestIP) location = locationHelper.getLocation(requestIP)
countryLetters = locationHelper.getCountry(requestIP) countryLetters = locationHelper.getCountry(requestIP)
country = countryHelper.getCountryID(countryLetters) country = countryHelper.getCountryID(countryLetters)
else: else:
# Set location to 0,0 and get country from db # Set location to 0,0 and get country from db
log.warning("Location skipped") log.warning("Location skipped")
latitude = 0 location = [0,0]
longitude = 0
countryLetters = "XX" countryLetters = "XX"
country = countryHelper.getCountryID(userUtils.getCountry(userID)) country = countryHelper.getCountryID(userHelper.getCountry(userID))
# Set location and country # Set location and country
responseToken.setLocation(latitude, longitude) responseToken.setLocation(location)
responseToken.country = country responseToken.setCountry(country)
# Set country in db if user has no country (first bancho login) # Set country in db if user has no country (first bancho login)
if userUtils.getCountry(userID) == "XX": if userHelper.getCountry(userID) == "XX":
userUtils.setCountry(userID, countryLetters) userHelper.setCountry(userID, countryLetters)
# Send to everyone our userpanel if we are not restricted or tournament # Send to everyone our userpanel if we are not restricted
if not responseToken.restricted: if responseToken.restricted == False:
glob.streams.broadcast("main", serverPackets.userPanel(userID)) glob.tokens.enqueueAll(serverPackets.userPanel(userID))
# Set reponse data to right value and reset our queue # Set reponse data to right value and reset our queue
responseData = responseToken.queue responseData = responseToken.queue
@ -224,23 +207,21 @@ def handle(tornadoRequest):
except exceptions.loginFailedException: except exceptions.loginFailedException:
# Login failed error packet # Login failed error packet
# (we don't use enqueue because we don't have a token since login has failed) # (we don't use enqueue because we don't have a token since login has failed)
err = True
responseData += serverPackets.loginFailed() responseData += serverPackets.loginFailed()
except exceptions.invalidArgumentsException: except exceptions.haxException:
# Invalid POST data # Invalid POST data
# (we don't use enqueue because we don't have a token since login has failed) # (we don't use enqueue because we don't have a token since login has failed)
err = True
responseData += serverPackets.loginFailed() responseData += serverPackets.loginFailed()
responseData += serverPackets.notification("I see what you're doing...") responseData += serverPackets.notification("I see what you're doing...")
except exceptions.loginBannedException: except exceptions.loginBannedException:
# Login banned error packet # Login banned error packet
err = True
responseData += serverPackets.loginBanned() responseData += serverPackets.loginBanned()
except exceptions.loginLockedException:
# Login banned error packet
responseData += serverPackets.loginLocked()
except exceptions.banchoMaintenanceException: except exceptions.banchoMaintenanceException:
# Bancho is in maintenance mode # Bancho is in maintenance mode
responseData = bytes() responseData = responseToken.queue
if responseToken is not None:
responseData = responseToken.queue
responseData += serverPackets.notification("Our bancho server is in maintenance mode. Please try to login again later.") responseData += serverPackets.notification("Our bancho server is in maintenance mode. Please try to login again later.")
responseData += serverPackets.loginFailed() responseData += serverPackets.loginFailed()
except exceptions.banchoRestartingException: except exceptions.banchoRestartingException:
@ -253,14 +234,18 @@ def handle(tornadoRequest):
except exceptions.haxException: except exceptions.haxException:
# Using oldoldold client, we don't have client data. Force update. # Using oldoldold client, we don't have client data. Force update.
# (we don't use enqueue because we don't have a token since login has failed) # (we don't use enqueue because we don't have a token since login has failed)
err = True
responseData += serverPackets.forceUpdate() responseData += serverPackets.forceUpdate()
responseData += serverPackets.notification("Hory shitto, your client is TOO old! Nice prehistory! Please turn update it from the settings!") responseData += serverPackets.notification("Hory shitto, your client is TOO old! Nice preistoria! Please turn off the switcher and update it.")
except: except:
log.error("Unknown error!\n```\n{}\n{}```".format(sys.exc_info(), traceback.format_exc())) log.error("Unknown error!\n```\n{}\n{}```".format(sys.exc_info(), traceback.format_exc()))
finally: finally:
# Console and discord log # Console and discord log
if len(loginData) < 3: if len(loginData) < 3:
log.info("Invalid bancho login request from **{}** (insufficient POST data)".format(requestIP), "bunker") msg = "Invalid bancho login request from **{}** (insufficient POST data)".format(requestIP)
else:
msg = "Bancho login request from **{}** for user **{}** ({})".format(requestIP, loginData[0], "failed" if err == True else "success")
log.info(msg, "bunker")
# Return token string and data # Return token string and data
return responseTokenString, responseData return (responseTokenString, responseData)

View File

@ -1,13 +1,12 @@
import time
import json
from common.log import logUtils as log
from constants import serverPackets
from helpers import chatHelper as chat
from objects import glob from objects import glob
from helpers import consoleHelper
from constants import bcolors
from constants import serverPackets
import time
from helpers import logHelper as log
from helpers import chatHelper as chat
def handle(userToken, _=None):
def handle(userToken, _=None, deleteToken=True):
# get usertoken data # get usertoken data
userID = userToken.userID userID = userToken.userID
username = userToken.username username = userToken.username
@ -17,41 +16,31 @@ def handle(userToken, _=None, deleteToken=True):
# the old logout packet will still be in the queue and will be sent to # the old logout packet will still be in the queue and will be sent to
# the server, so we accept logout packets sent at least 5 seconds after login # the server, so we accept logout packets sent at least 5 seconds after login
# if the user logs out before 5 seconds, he will be disconnected later with timeout check # if the user logs out before 5 seconds, he will be disconnected later with timeout check
if int(time.time() - userToken.loginTime) >= 5 or userToken.irc: if int(time.time()-userToken.loginTime) >= 5 or userToken.irc == True:
# Stop spectating # Stop spectating if needed
userToken.stopSpectating() # TODO: Call stopSpectatingEvent!!!!!!!!!
if userToken.spectating != 0:
# The user was spectating someone
spectatorHostToken = glob.tokens.getTokenFromUserID(userToken.spectating)
if spectatorHostToken != None:
# The host is still online, send removeSpectator to him
spectatorHostToken.enqueue(serverPackets.removeSpectator(userID))
# Part matches
userToken.leaveMatch()
# Part all joined channels # Part all joined channels
for i in userToken.joinedChannels: for i in userToken.joinedChannels:
chat.partChannel(token=userToken, channel=i) chat.partChannel(token=userToken, channel=i)
# Leave all joined streams # TODO: Lobby left if joined
userToken.leaveAllStreams()
# Enqueue our disconnection to everyone else # Enqueue our disconnection to everyone else
glob.streams.broadcast("main", serverPackets.userLogout(userID)) glob.tokens.enqueueAll(serverPackets.userLogout(userID))
# Disconnect from IRC if needed # Disconnect from IRC if needed
if userToken.irc and glob.irc: if userToken.irc == True and glob.irc == True:
glob.ircServer.forceDisconnection(userToken.username) glob.ircServer.forceDisconnection(userToken.username)
# Delete token # Delete token
if deleteToken: glob.tokens.deleteToken(requestToken)
glob.tokens.deleteToken(requestToken)
else:
userToken.kicked = True
# Change username if needed
newUsername = glob.redis.get("ripple:change_username_pending:{}".format(userID))
if newUsername is not None:
log.debug("Sending username change request for user {}".format(userID))
glob.redis.publish("peppy:change_username", json.dumps({
"userID": userID,
"newUsername": newUsername.decode("utf-8")
}))
# Console output # Console output
log.info("{} has been disconnected. (logout)".format(username)) log.info("{} has been disconnected.".format(username))

View File

@ -1,6 +1,6 @@
from objects import glob from objects import glob
def handle(userToken, _, has): def handle(userToken, packetData, has):
# Get usertoken data # Get usertoken data
userID = userToken.userID userID = userToken.userID
@ -15,6 +15,8 @@ def handle(userToken, _, has):
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
return return
# The match exists, get object
match = glob.matches.matches[matchID]
# Set has beatmap/no beatmap # Set has beatmap/no beatmap
with glob.matches.matches[matchID] as match: match.userHasBeatmap(userID, has)
match.userHasBeatmap(userID, has)

View File

@ -15,6 +15,8 @@ def handle(userToken, _):
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
return return
# Get match object
match = glob.matches.matches[matchID]
# Change team # Change team
with glob.matches.matches[matchID] as match: match.changeTeam(userID)
match.changeTeam(userID)

View File

@ -1,6 +1,6 @@
from objects import glob from objects import glob
def handle(userToken, _): def handle(userToken, packetData):
# Get usertoken data # Get usertoken data
userID = userToken.userID userID = userToken.userID
@ -15,6 +15,8 @@ def handle(userToken, _):
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
return return
# The match exists, get object
match = glob.matches.matches[matchID]
# Set our match complete # Set our match complete
with glob.matches.matches[matchID] as match: match.playerCompleted(userID)
match.playerCompleted(userID)

View File

@ -15,6 +15,8 @@ def handle(userToken, _):
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
return return
# Match exists, get object
match = glob.matches.matches[matchID]
# Fail user # Fail user
with glob.matches.matches[matchID] as match: match.playerFailed(userID)
match.playerFailed(userID)

View File

@ -1,5 +1,7 @@
from objects import glob from objects import glob
from constants import serverPackets, clientPackets from constants import slotStatuses
from constants import serverPackets
from helpers import logHelper as log
def handle(userToken, packetData): def handle(userToken, packetData):
# Get usertoken data # Get usertoken data
@ -16,16 +18,15 @@ def handle(userToken, packetData):
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
return return
# Parse the data # The match exists, get object
data = clientPackets.matchFrames(packetData) match = glob.matches.matches[matchID]
with glob.matches.matches[matchID] as match: # Change slot id in packetData
# Change slot id in packetData slotID = match.getUserSlotID(userID)
slotID = match.getUserSlotID(userID)
# Update the score # Enqueue frames to who's playing
match.updateScore(slotID, data["totalScore"]) for i in range(0,16):
match.updateHP(slotID, data["currentHp"]) if match.slots[i]["userID"] > -1 and match.slots[i]["status"] == slotStatuses.playing:
token = glob.tokens.getTokenFromUserID(match.slots[i]["userID"])
# Enqueue frames to who's playing if token != None:
glob.streams.broadcast(match.playingStreamName, serverPackets.matchFrames(slotID, packetData)) token.enqueue(serverPackets.matchFrames(slotID, packetData))

View File

@ -1,4 +1,3 @@
from events import matchBeatmapEvent from events import matchBeatmapEvent
def handle(userToken, packetData): def handle(userToken, packetData):
matchBeatmapEvent.handle(userToken, packetData, True) matchBeatmapEvent.handle(userToken, packetData, True)

View File

@ -17,6 +17,8 @@ def handle(userToken, packetData):
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
return return
# Get match object
match = glob.matches.matches[matchID]
# Send invite # Send invite
with glob.matches.matches[matchID] as match: match.invite(userID, packetData["userID"])
match.invite(userID, packetData["userID"])

View File

@ -12,16 +12,12 @@ def handle(userToken, packetData):
matchID = userToken.matchID matchID = userToken.matchID
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
return return
match = glob.matches.matches[matchID]
with glob.matches.matches[matchID] as match: # Make sure we aren't locking our slot
# Host check ourSlot = match.getUserSlotID(userID)
if userID != match.hostUserID: if packetData["slotID"] == ourSlot:
return return
# Make sure we aren't locking our slot # Lock/Unlock slot
ourSlot = match.getUserSlotID(userID) match.toggleSlotLock(packetData["slotID"])
if packetData["slotID"] == ourSlot:
return
# Lock/Unlock slot
match.toggleSlotLocked(packetData["slotID"])

View File

@ -1,4 +1,3 @@
from events import matchBeatmapEvent from events import matchBeatmapEvent
def handle(userToken, packetData): def handle(userToken, packetData):
matchBeatmapEvent.handle(userToken, packetData, False) matchBeatmapEvent.handle(userToken, packetData, False)

View File

@ -1,6 +1,6 @@
from objects import glob from objects import glob
def handle(userToken, _): def handle(userToken, packetData):
# Get userToken data # Get userToken data
userID = userToken.userID userID = userToken.userID
@ -15,6 +15,8 @@ def handle(userToken, _):
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
return return
# The match exists, get object
match = glob.matches.matches[matchID]
# Set our load status # Set our load status
with glob.matches.matches[matchID] as match: match.playerLoaded(userID)
match.playerLoaded(userID)

View File

@ -8,14 +8,9 @@ def handle(userToken, _):
matchID = userToken.matchID matchID = userToken.matchID
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
return return
match = glob.matches.matches[matchID]
with glob.matches.matches[matchID] as match: # Get our slotID and change ready status
# Get our slotID and change ready status slotID = match.getUserSlotID(userID)
slotID = match.getUserSlotID(userID) if slotID != None:
if slotID is not None: match.toggleSlotReady(slotID)
match.toggleSlotReady(slotID)
# If this is a tournament match, we should send the current status of ready
# players.
if match.isTourney:
match.sendReadyStatus()

View File

@ -1,6 +1,6 @@
from objects import glob from objects import glob
def handle(userToken, _): def handle(userToken, packetData):
# Get userToken data # Get userToken data
userID = userToken.userID userID = userToken.userID
@ -15,6 +15,8 @@ def handle(userToken, _):
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
return return
# The match exists, get object
match = glob.matches.matches[matchID]
# Skip # Skip
with glob.matches.matches[matchID] as match: match.playerSkip(userID)
match.playerSkip(userID)

View File

@ -1,6 +1,9 @@
from objects import glob from objects import glob
from constants import slotStatuses
from constants import serverPackets
def handle(userToken, _): def handle(userToken, _):
# TODO: Host check
# Get match ID and match object # Get match ID and match object
matchID = userToken.matchID matchID = userToken.matchID
@ -13,9 +16,32 @@ def handle(userToken, _):
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
return return
with glob.matches.matches[matchID] as match: # The match exists, get object
# Host check match = glob.matches.matches[matchID]
if userToken.userID != match.hostUserID:
return
match.start() force = False # TODO: Force thing
# Make sure we have enough players
if (match.countUsers() < 2 or not match.checkTeams()) and not force:
return
# Change inProgress value
match.inProgress = True
# Set playing to ready players and set load, skip and complete to False
for i in range(0,16):
if (match.slots[i]["status"] & slotStatuses.ready) > 0:
match.slots[i]["status"] = slotStatuses.playing
match.slots[i]["loaded"] = False
match.slots[i]["skip"] = False
match.slots[i]["complete"] = False
# Send match start packet
for i in range(0,16):
if (match.slots[i]["status"] & slotStatuses.playing) > 0 and match.slots[i]["userID"] != -1:
token = glob.tokens.getTokenFromUserID(match.slots[i]["userID"])
if token != None:
token.enqueue(serverPackets.matchStart(matchID))
# Send updates
match.sendUpdate()

View File

@ -16,10 +16,8 @@ def handle(userToken, packetData):
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
return return
# Host check # Match exists, get object
with glob.matches.matches[matchID] as match: match = glob.matches.matches[matchID]
if userToken.userID != match.hostUserID:
return
# Transfer host # Transfer host
match.transferHost(packetData["slotID"]) match.transferHost(packetData["slotID"])

View File

@ -1,15 +1,17 @@
from common.log import logUtils as log from objects import glob
from events import channelPartEvent
from helpers import logHelper as log
from helpers import chatHelper as chat from helpers import chatHelper as chat
def handle(userToken, _): def handle(userToken, _):
# Get usertoken data # Get usertoken data
userID = userToken.userID
username = userToken.username username = userToken.username
# Remove user from users in lobby # Remove user from users in lobby
userToken.leaveStream("lobby") glob.matches.lobbyUserPart(userID)
# Part lobby channel # Part lobby channel
# Done automatically by the client
chat.partChannel(channel="#lobby", token=userToken, kick=True) chat.partChannel(channel="#lobby", token=userToken, kick=True)
# Console output # Console output

View File

@ -1,2 +1,29 @@
def handle(userToken, _=None): from objects import glob
userToken.leaveMatch() from helpers import chatHelper as chat
def handle(userToken, _):
# get data from usertoken
userID = userToken.userID
# Get match ID and match object
matchID = userToken.matchID
# Make sure we are in a match
if matchID == -1:
return
# Make sure the match exists
if matchID not in glob.matches.matches:
return
# The match exists, get object
match = glob.matches.matches[matchID]
# Set slot to free
match.userLeft(userID)
# Part #multiplayer channel
#chat.partChannel(token=userToken, channel="#multi_{}".format(matchID), kick=True)
# Set usertoken match to -1
userToken.partMatch()

View File

@ -1,7 +1,11 @@
from constants import clientPackets
from constants import serverPackets from constants import serverPackets
from helpers import userHelper
from helpers import logHelper as log
def handle(userToken, packetData): def handle(userToken, packetData):
log.debug("Requested status update")
# Update cache and send new stats # Update cache and send new stats
userToken.updateCachedStats() userToken.updateCachedStats()
userToken.enqueue(serverPackets.userStats(userToken.userID)) userToken.enqueue(serverPackets.userStats(userToken.userID))

View File

@ -1,8 +1,6 @@
from common.log import logUtils as log
from constants import clientPackets from constants import clientPackets
from constants import serverPackets from constants import serverPackets
from objects import glob from helpers import logHelper as log
def handle(userToken, packetData): def handle(userToken, packetData):
# get token data # get token data
@ -12,12 +10,12 @@ def handle(userToken, packetData):
packetData = clientPackets.setAwayMessage(packetData) packetData = clientPackets.setAwayMessage(packetData)
# Set token away message # Set token away message
userToken.awayMessage = packetData["awayMessage"] userToken.setAwayMessage(packetData["awayMessage"])
# Send private message from fokabot # Send private message from fokabot
if packetData["awayMessage"] == "": if packetData["awayMessage"] == "":
fokaMessage = "Your away message has been reset" fokaMessage = "Your away message has been reset"
else: else:
fokaMessage = "Your away message is now: {}".format(packetData["awayMessage"]) fokaMessage = "Your away message is now: {}".format(packetData["awayMessage"])
userToken.enqueue(serverPackets.sendMessage(glob.BOT_NAME, username, fokaMessage)) userToken.enqueue(serverPackets.sendMessage("FokaBot", username, fokaMessage))
log.info("{} has changed their away message to: {}".format(username, packetData["awayMessage"])) log.info("{} has changed their away message to: {}".format(username, packetData["awayMessage"]))

View File

@ -1,15 +1,30 @@
from objects import glob from objects import glob
from constants import serverPackets from constants import serverPackets
from common.log import logUtils as log from constants import exceptions
def handle(userToken, packetData): def handle(userToken, packetData):
# get token data # get token data
userID = userToken.userID userID = userToken.userID
# Send spectator frames to every spectator # Send spectator frames to every spectator
streamName = "spect/{}".format(userID) for i in userToken.spectators:
glob.streams.broadcast(streamName, serverPackets.spectatorFrames(packetData[7:])) # Send to every user but host
log.debug("Broadcasting {}'s frames to {} clients".format( if i != userID:
userID, try:
len(glob.streams.streams[streamName].clients)) # Get spectator token object
) spectatorToken = glob.tokens.getTokenFromUserID(i)
# Make sure the token exists
if spectatorToken == None:
raise exceptions.stopSpectating
# Make sure this user is spectating us
if spectatorToken.spectating != userID:
raise exceptions.stopSpectating
# Everything seems fine, send spectator frames to this spectator
spectatorToken.enqueue(serverPackets.spectatorFrames(packetData[7:]))
except exceptions.stopSpectating:
# Remove this user from spectators
userToken.removeSpectator(i)
userToken.enqueue(serverPackets.removeSpectator(i))

View File

@ -1,25 +1,56 @@
from common.log import logUtils as log
from constants import clientPackets from constants import clientPackets
from constants import serverPackets
from constants import exceptions from constants import exceptions
from objects import glob from objects import glob
from helpers import userHelper
from helpers import logHelper as log
from helpers import chatHelper as chat
def handle(userToken, packetData): def handle(userToken, packetData):
try: try:
# Get usertoken data
userID = userToken.userID
username = userToken.username
# Start spectating packet # Start spectating packet
packetData = clientPackets.startSpectating(packetData) packetData = clientPackets.startSpectating(packetData)
# If the user id is less than 0, treat this as a stop spectating packet # Stop spectating old user if needed
if packetData["userID"] < 0: if userToken.spectating != 0:
oldTargetToken = glob.tokens.getTokenFromUserID(userToken.spectating)
oldTargetToken.enqueue(serverPackets.removeSpectator(userID))
userToken.stopSpectating() userToken.stopSpectating()
return
# Start spectating new user
userToken.startSpectating(packetData["userID"])
# Get host token # Get host token
targetToken = glob.tokens.getTokenFromUserID(packetData["userID"]) targetToken = glob.tokens.getTokenFromUserID(packetData["userID"])
if targetToken is None: if targetToken == None:
raise exceptions.tokenNotFoundException raise exceptions.tokenNotFoundException
# Start spectating new user # Add us to host's spectators
userToken.startSpectating(targetToken) targetToken.addSpectator(userID)
# Send spectator join packet to host
targetToken.enqueue(serverPackets.addSpectator(userID))
# Create and join #spectator (#spect_userid) channel
glob.channels.addTempChannel("#spect_{}".format(targetToken.userID))
chat.joinChannel(token=userToken, channel="#spect_{}".format(targetToken.userID))
if len(targetToken.spectators) == 1:
# First spectator, send #spectator join to host too
chat.joinChannel(token=targetToken, channel="#spect_{}".format(targetToken.userID))
# send fellowSpectatorJoined to all spectators
for spec in targetToken.spectators:
if spec is not userID:
c = glob.tokens.getTokenFromUserID(spec)
userToken.enqueue(serverPackets.fellowSpectatorJoined(c.userID))
c.enqueue(serverPackets.fellowSpectatorJoined(userID))
# Console output
log.info("{} are spectating {}".format(username, userHelper.getUsername(packetData["userID"])))
except exceptions.tokenNotFoundException: except exceptions.tokenNotFoundException:
# Stop spectating if token not found # Stop spectating if token not found
log.warning("Spectator start: token not found") log.warning("Spectator start: token not found")

View File

@ -1,2 +1,37 @@
def handle(userToken, _=None): from objects import glob
userToken.stopSpectating() from constants import serverPackets
from constants import exceptions
from helpers import logHelper as log
from helpers import chatHelper as chat
def handle(userToken, _):
try:
# get user token data
userID = userToken.userID
username = userToken.username
# Remove our userID from host's spectators
target = userToken.spectating
targetToken = glob.tokens.getTokenFromUserID(target)
if targetToken == None:
raise exceptions.tokenNotFoundException
targetToken.removeSpectator(userID)
# Part #spectator channel
chat.partChannel(token=userToken, channel="#spect_{}".format(target))
# Send the spectator left packet to host
targetToken.enqueue(serverPackets.removeSpectator(userID))
for c in targetToken.spectators:
spec = glob.tokens.getTokenFromUserID(c)
spec.enqueue(serverPackets.fellowSpectatorLeft(userID))
#targetToken.enqueue(serverPackets.fellowSpectatorLeft(userID))
# Console output
log.info("{} are no longer spectating {}".format(username, target))
except exceptions.tokenNotFoundException:
log.warning("Spectator stop: token not found")
finally:
# Set our spectating user to 0
userToken.stopSpectating()

View File

@ -1,11 +0,0 @@
from constants import clientPackets
from objects import glob
from helpers import chatHelper as chat
def handle(userToken, packetData):
packetData = clientPackets.tournamentJoinMatchChannel(packetData)
matchID = packetData["matchID"]
if matchID not in glob.matches.matches or not userToken.tournament:
return
userToken.matchID = matchID
chat.joinChannel(token=userToken, channel="#multi_{}".format(matchID), force=True)

View File

@ -1,11 +0,0 @@
from constants import clientPackets
from objects import glob
from helpers import chatHelper as chat
def handle(userToken, packetData):
packetData = clientPackets.tournamentLeaveMatchChannel(packetData)
matchID = packetData["matchID"]
if matchID not in glob.matches.matches or not userToken.tournament:
return
chat.partChannel(token=userToken, channel="#multi_{}".format(matchID), force=True)
userToken.matchID = 0

View File

@ -1,10 +0,0 @@
from constants import clientPackets
from objects import glob
def handle(userToken, packetData):
packetData = clientPackets.tournamentMatchInfoRequest(packetData)
matchID = packetData["matchID"]
if matchID not in glob.matches.matches or not userToken.tournament:
return
with glob.matches.matches[matchID] as m:
userToken.enqueue(m.matchDataCache)

View File

@ -1,7 +1,6 @@
from common.log import logUtils as log
from constants import clientPackets from constants import clientPackets
from constants import serverPackets from constants import serverPackets
from helpers import logHelper as log
def handle(userToken, packetData): def handle(userToken, packetData):
# Read userIDs list # Read userIDs list

View File

@ -1,7 +1,6 @@
from common.log import logUtils as log
from constants import clientPackets from constants import clientPackets
from constants import serverPackets from constants import serverPackets
from helpers import logHelper as log
def handle(userToken, packetData): def handle(userToken, packetData):
# Read userIDs list # Read userIDs list

View File

@ -1,23 +1,16 @@
fuck=firetruck fuck=firetruck
shit=shish shit=shish
ass=peach ass=peach
asses=peaches asses=peaches
bitch=fine lady bitch=fine lady
bitches=fine ladies bitches=fine ladies
asshole=donkey asshole=donkey
ass hole=donkey ass hole=donkey
cock=chicken cock=chicken
cocks=chickens cocks=chickens
dick=eggplant dick=eggplant
dicks=eggplants dicks=eggplants
boobs=bob boobs=bob
tits=teeth tits=teeth
cum=yogurt cum=yogurt
cunt=count cunt=count
nigger=flowers
ngger=flowers
niggers=flowers
weed=grass
AQN=meme
theaquila=meme
aquila=meme

View File

@ -1,4 +0,0 @@
find . -name "*.c" -type f -delete
find . -name "*.o" -type f -delete
find . -name "*.so" -type f -delete
python3 setup.py build_ext --inplace

View File

@ -1,46 +1,23 @@
import json from helpers import requestHelper
import tornado.web
import tornado.gen
from common.sentry import sentry
from common.ripple import userUtils
from common.web import requestsManager
from constants import exceptions from constants import exceptions
import json
from objects import glob from objects import glob
class handler(requestHelper.asyncRequestHandler):
class handler(requestsManager.asyncRequestHandler):
@tornado.web.asynchronous
@tornado.gen.engine
@sentry.captureTornado
def asyncGet(self): def asyncGet(self):
statusCode = 400 statusCode = 400
data = {"message": "unknown error"} data = {"message": "unknown error"}
try: try:
# Check arguments # Check arguments
if "u" not in self.request.arguments and "id" not in self.request.arguments: if requestHelper.checkArguments(self.request.arguments, ["u"]) == False:
raise exceptions.invalidArgumentsException() raise exceptions.invalidArgumentsException()
# Get online staus # Get online staus
username = None username = self.get_argument("u")
userID = None if username == None:
if "u" in self.request.arguments:
#username = self.get_argument("u").lower().replace(" ", "_")
username = userUtils.safeUsername(self.get_argument("u"))
else:
try:
userID = int(self.get_argument("id"))
except:
raise exceptions.invalidArgumentsException()
if username is None and userID is None:
data["result"] = False data["result"] = False
else: else:
if username is not None: data["result"] = True if glob.tokens.getTokenFromUsername(username) != None else False
data["result"] = True if glob.tokens.getTokenFromUsername(username, safe=True) is not None else False
else:
data["result"] = True if glob.tokens.getTokenFromUserID(userID) is not None else False
# Status code and message # Status code and message
statusCode = 200 statusCode = 200
@ -53,5 +30,7 @@ class handler(requestsManager.asyncRequestHandler):
data["status"] = statusCode data["status"] = statusCode
# Send response # Send response
#self.clear()
self.write(json.dumps(data)) self.write(json.dumps(data))
self.set_status(statusCode) self.set_status(statusCode)
#self.finish(json.dumps(data))

View File

@ -1,23 +1,14 @@
from helpers import requestHelper
import json import json
import tornado.web
import tornado.gen
from common.sentry import sentry
from common.web import requestsManager
from objects import glob from objects import glob
class handler(requestHelper.asyncRequestHandler):
class handler(requestsManager.asyncRequestHandler):
@tornado.web.asynchronous
@tornado.gen.engine
@sentry.captureTornado
def asyncGet(self): def asyncGet(self):
statusCode = 400 statusCode = 400
data = {"message": "unknown error"} data = {"message": "unknown error"}
try: try:
# Get online users count # Get online users count
data["result"] = int(glob.redis.get("ripple:online_users").decode("utf-8")) data["result"] = len(glob.tokens.tokens)
# Status code and message # Status code and message
statusCode = 200 statusCode = 200
@ -27,5 +18,7 @@ class handler(requestsManager.asyncRequestHandler):
data["status"] = statusCode data["status"] = statusCode
# Send response # Send response
#self.clear()
self.write(json.dumps(data)) self.write(json.dumps(data))
self.set_status(statusCode) self.set_status(statusCode)
#self.finish(json.dumps(data))

View File

@ -1,23 +1,14 @@
from helpers import requestHelper
import json import json
import tornado.web
import tornado.gen
from common.sentry import sentry
from common.web import requestsManager
from objects import glob from objects import glob
class handler(requestHelper.asyncRequestHandler):
class handler(requestsManager.asyncRequestHandler):
@tornado.web.asynchronous
@tornado.gen.engine
@sentry.captureTornado
def asyncGet(self): def asyncGet(self):
statusCode = 400 statusCode = 400
data = {"message": "unknown error"} data = {"message": "unknown error"}
try: try:
# Get online users count # Get online users count
data["result"] = -1 if glob.restarting else 1 data["result"] = -1 if glob.restarting == True else 1
# Status code and message # Status code and message
statusCode = 200 statusCode = 200
@ -27,5 +18,7 @@ class handler(requestsManager.asyncRequestHandler):
data["status"] = statusCode data["status"] = statusCode
# Send response # Send response
#self.clear()
self.write(json.dumps(data)) self.write(json.dumps(data))
self.set_status(statusCode) self.set_status(statusCode)
#self.finish(json.dumps(data))

View File

@ -1,24 +1,16 @@
from helpers import requestHelper
from helpers import logHelper as log
import json import json
import tornado.web
import tornado.gen
from common.sentry import sentry
from common.web import requestsManager
from constants import exceptions
from objects import glob from objects import glob
from constants import exceptions
class handler(requestHelper.asyncRequestHandler):
class handler(requestsManager.asyncRequestHandler):
@tornado.web.asynchronous
@tornado.gen.engine
@sentry.captureTornado
def asyncGet(self): def asyncGet(self):
statusCode = 400 statusCode = 400
data = {"message": "unknown error"} data = {"message": "unknown error"}
try: try:
# Check arguments # Check arguments
if not requestsManager.checkArguments(self.request.arguments, ["u"]): if requestHelper.checkArguments(self.request.arguments, ["u"]) == False:
raise exceptions.invalidArgumentsException() raise exceptions.invalidArgumentsException()
# Get userID and its verified cache thing # Get userID and its verified cache thing

View File

@ -1,26 +1,17 @@
import json from helpers import requestHelper
import tornado.web
import tornado.gen
from common.sentry import sentry
from common.log import logUtils as log
from common.web import requestsManager
from constants import exceptions from constants import exceptions
from helpers import systemHelper import json
from objects import glob from objects import glob
from helpers import systemHelper
from helpers import logHelper as log
class handler(requestHelper.asyncRequestHandler):
class handler(requestsManager.asyncRequestHandler):
@tornado.web.asynchronous
@tornado.gen.engine
@sentry.captureTornado
def asyncGet(self): def asyncGet(self):
statusCode = 400 statusCode = 400
data = {"message": "unknown error"} data = {"message": "unknown error"}
try: try:
# Check arguments # Check arguments
if not requestsManager.checkArguments(self.request.arguments, ["k"]): if requestHelper.checkArguments(self.request.arguments, ["k"]) == False:
raise exceptions.invalidArgumentsException() raise exceptions.invalidArgumentsException()
# Check ci key # Check ci key
@ -42,5 +33,7 @@ class handler(requestsManager.asyncRequestHandler):
data["status"] = statusCode data["status"] = statusCode
# Send response # Send response
#self.clear()
self.write(json.dumps(data)) self.write(json.dumps(data))
self.set_status(statusCode) self.set_status(statusCode)
#self.finish(json.dumps(data))

View File

@ -1,48 +1,39 @@
import json from helpers import requestHelper
from constants import exceptions
import tornado.web import json
import tornado.gen from objects import glob
from helpers import chatHelper
from common.sentry import sentry from helpers import logHelper as log
from common.web import requestsManager
from constants import exceptions class handler(requestHelper.asyncRequestHandler):
from helpers import chatHelper def asyncGet(self):
from objects import glob statusCode = 400
data = {"message": "unknown error"}
try:
class handler(requestsManager.asyncRequestHandler): # Check arguments
@tornado.web.asynchronous if requestHelper.checkArguments(self.request.arguments, ["k", "to", "msg"]) == False:
@tornado.gen.engine raise exceptions.invalidArgumentsException()
@sentry.captureTornado
def asyncGet(self): # Check ci key
statusCode = 400 key = self.get_argument("k")
data = {"message": "unknown error"} if key is None or key != glob.conf.config["server"]["cikey"]:
try: raise exceptions.invalidArgumentsException()
# Check arguments
if not requestsManager.checkArguments(self.request.arguments, ["k", "to", "msg"]): log.info("API REQUEST FOR FOKABOT MESSAGE AAAAAAA")
raise exceptions.invalidArgumentsException() chatHelper.sendMessage("FokaBot", self.get_argument("to"), self.get_argument("msg"))
# Check ci key # Status code and message
key = self.get_argument("k") statusCode = 200
if key is None or key != glob.conf.config["server"]["cikey"]: data["message"] = "ok"
raise exceptions.invalidArgumentsException() except exceptions.invalidArgumentsException:
statusCode = 400
chatHelper.sendMessage( data["message"] = "invalid parameters"
glob.BOT_NAME, finally:
self.get_argument("to").encode().decode("ASCII", "ignore"), # Add status code to data
self.get_argument("msg").encode().decode("ASCII", "ignore") data["status"] = statusCode
)
# Send response
# Status code and message #self.clear()
statusCode = 200 self.write(json.dumps(data))
data["message"] = "ok" self.set_status(statusCode)
except exceptions.invalidArgumentsException: #self.finish(json.dumps(data))
statusCode = 400
data["message"] = "invalid parameters"
finally:
# Add status code to data
data["status"] = statusCode
# Send response
self.write(json.dumps(data))
self.set_status(statusCode)

View File

@ -1,25 +0,0 @@
import tornado.gen
import tornado.web
from common.web import requestsManager
from objects import glob
import time
class handler(requestsManager.asyncRequestHandler):
@tornado.web.asynchronous
@tornado.gen.engine
def asyncGet(self):
if not glob.debug:
self.write("Nope")
return
time.sleep(0.5)
self.write("meemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeem")
self.set_status(200)
self.add_header("cho-token", "tua madre")
self.add_header("cho-protocol", "19")
self.add_header("Connection", "keep-alive")
self.add_header("Keep-Alive", "timeout=5, max=100")
self.add_header("Content-Type", "text/html; charset=UTF-8")
#glob.db.fetchAll("SELECT SQL_NO_CACHE * FROM beatmaps")
#glob.db.fetchAll("SELECT SQL_NO_CACHE * FROM users")
#glob.db.fetchAll("SELECT SQL_NO_CACHE * FROM scores")
#self.write("ibmd")

264
handlers/mainHandler.py Normal file
View File

@ -0,0 +1,264 @@
import datetime
import gzip
from helpers import requestHelper
from objects import glob
from constants import exceptions
from constants import packetIDs
from helpers import packetHelper
from constants import serverPackets
from events import sendPublicMessageEvent
from events import sendPrivateMessageEvent
from events import channelJoinEvent
from events import channelPartEvent
from events import changeActionEvent
from events import cantSpectateEvent
from events import startSpectatingEvent
from events import stopSpectatingEvent
from events import spectateFramesEvent
from events import friendAddEvent
from events import friendRemoveEvent
from events import logoutEvent
from events import loginEvent
from events import setAwayMessageEvent
from events import joinLobbyEvent
from events import createMatchEvent
from events import partLobbyEvent
from events import changeSlotEvent
from events import joinMatchEvent
from events import partMatchEvent
from events import changeMatchSettingsEvent
from events import changeMatchPasswordEvent
from events import changeMatchModsEvent
from events import matchReadyEvent
from events import matchLockEvent
from events import matchStartEvent
from events import matchPlayerLoadEvent
from events import matchSkipEvent
from events import matchFramesEvent
from events import matchCompleteEvent
from events import matchNoBeatmapEvent
from events import matchHasBeatmapEvent
from events import matchTransferHostEvent
from events import matchFailedEvent
from events import matchInviteEvent
from events import matchChangeTeamEvent
from events import userStatsRequestEvent
from events import requestStatusUpdateEvent
from events import userPanelRequestEvent
# Exception tracking
import tornado.web
import tornado.gen
import sys
import traceback
from raven.contrib.tornado import SentryMixin
from helpers import logHelper as log
class handler(SentryMixin, requestHelper.asyncRequestHandler):
@tornado.web.asynchronous
@tornado.gen.engine
def asyncPost(self):
try:
# Track time if needed
if glob.outputRequestTime == True:
# Start time
st = datetime.datetime.now()
# Client's token string and request data
requestTokenString = self.request.headers.get("osu-token")
requestData = self.request.body
# Server's token string and request data
responseTokenString = "ayy"
responseData = bytes()
if requestTokenString == None:
# No token, first request. Handle login.
responseTokenString, responseData = loginEvent.handle(self)
else:
userToken = None # default value
try:
# This is not the first packet, send response based on client's request
# Packet start position, used to read stacked packets
pos = 0
# Make sure the token exists
if requestTokenString not in glob.tokens.tokens:
raise exceptions.tokenNotFoundException()
# Token exists, get its object and lock it
userToken = glob.tokens.tokens[requestTokenString]
userToken.lock.acquire()
# Keep reading packets until everything has been read
while pos < len(requestData):
# Get packet from stack starting from new packet
leftData = requestData[pos:]
# Get packet ID, data length and data
packetID = packetHelper.readPacketID(leftData)
dataLength = packetHelper.readPacketLength(leftData)
packetData = requestData[pos:(pos+dataLength+7)]
# Console output if needed
if glob.outputPackets == True and packetID != 4:
log.debug("Incoming packet ({})({}):\n\nPacket code: {}\nPacket length: {}\nSingle packet data: {}\n".format(requestTokenString, userToken.username, str(packetID), str(dataLength), str(packetData)))
# Event handler
def handleEvent(ev):
def wrapper():
ev.handle(userToken, packetData)
return wrapper
eventHandler = {
# TODO: Rename packets and events
# TODO: Host check for multi
packetIDs.client_changeAction: handleEvent(changeActionEvent),
packetIDs.client_logout: handleEvent(logoutEvent),
packetIDs.client_friendAdd: handleEvent(friendAddEvent),
packetIDs.client_friendRemove: handleEvent(friendRemoveEvent),
packetIDs.client_userStatsRequest: handleEvent(userStatsRequestEvent),
packetIDs.client_requestStatusUpdate: handleEvent(requestStatusUpdateEvent),
packetIDs.client_userPanelRequest: handleEvent(userPanelRequestEvent),
packetIDs.client_channelJoin: handleEvent(channelJoinEvent),
packetIDs.client_channelPart: handleEvent(channelPartEvent),
packetIDs.client_sendPublicMessage: handleEvent(sendPublicMessageEvent),
packetIDs.client_sendPrivateMessage: handleEvent(sendPrivateMessageEvent),
packetIDs.client_setAwayMessage: handleEvent(setAwayMessageEvent),
packetIDs.client_startSpectating: handleEvent(startSpectatingEvent),
packetIDs.client_stopSpectating: handleEvent(stopSpectatingEvent),
packetIDs.client_cantSpectate: handleEvent(cantSpectateEvent),
packetIDs.client_spectateFrames: handleEvent(spectateFramesEvent),
packetIDs.client_joinLobby: handleEvent(joinLobbyEvent),
packetIDs.client_partLobby: handleEvent(partLobbyEvent),
packetIDs.client_createMatch: handleEvent(createMatchEvent),
packetIDs.client_joinMatch: handleEvent(joinMatchEvent),
packetIDs.client_partMatch: handleEvent(partMatchEvent),
packetIDs.client_matchChangeSlot: handleEvent(changeSlotEvent),
packetIDs.client_matchChangeSettings: handleEvent(changeMatchSettingsEvent),
packetIDs.client_matchChangePassword: handleEvent(changeMatchPasswordEvent),
packetIDs.client_matchChangeMods: handleEvent(changeMatchModsEvent),
packetIDs.client_matchReady: handleEvent(matchReadyEvent),
packetIDs.client_matchNotReady: handleEvent(matchReadyEvent),
packetIDs.client_matchLock: handleEvent(matchLockEvent),
packetIDs.client_matchStart: handleEvent(matchStartEvent),
packetIDs.client_matchLoadComplete: handleEvent(matchPlayerLoadEvent),
packetIDs.client_matchSkipRequest: handleEvent(matchSkipEvent),
packetIDs.client_matchScoreUpdate: handleEvent(matchFramesEvent),
packetIDs.client_matchComplete: handleEvent(matchCompleteEvent),
packetIDs.client_matchNoBeatmap: handleEvent(matchNoBeatmapEvent),
packetIDs.client_matchHasBeatmap: handleEvent(matchHasBeatmapEvent),
packetIDs.client_matchTransferHost: handleEvent(matchTransferHostEvent),
packetIDs.client_matchFailed: handleEvent(matchFailedEvent),
packetIDs.client_matchChangeTeam: handleEvent(matchChangeTeamEvent),
packetIDs.client_invite: handleEvent(matchInviteEvent),
}
# Packets processed if in restricted mode.
# All other packets will be ignored if the user is in restricted mode
packetsRestricted = [
packetIDs.client_logout,
packetIDs.client_userStatsRequest,
packetIDs.client_requestStatusUpdate,
packetIDs.client_userPanelRequest,
packetIDs.client_changeAction,
packetIDs.client_channelJoin,
packetIDs.client_channelPart,
]
# Process/ignore packet
if packetID != 4:
if packetID in eventHandler:
if userToken.restricted == False or (userToken.restricted == True and packetID in packetsRestricted):
eventHandler[packetID]()
else:
log.warning("Ignored packet id from {} ({}) (user is restricted)".format(requestTokenString, packetID))
else:
log.warning("Unknown packet id from {} ({})".format(requestTokenString, packetID))
# Update pos so we can read the next stacked packet
# +7 because we add packet ID bytes, unused byte and data length bytes
pos += dataLength+7
# Token queue built, send it
responseTokenString = userToken.token
responseData = userToken.queue
userToken.resetQueue()
# Update ping time for timeout
userToken.updatePingTime()
except exceptions.tokenNotFoundException:
# Token not found. Disconnect that user
responseData = serverPackets.loginError()
responseData += serverPackets.notification("Whoops! Something went wrong, please login again.")
log.warning("Received packet from unknown token ({}).".format(requestTokenString))
log.info("{} has been disconnected (invalid token)".format(requestTokenString))
finally:
# Unlock token
if userToken != None:
userToken.lock.release()
if glob.outputRequestTime == True:
# End time
et = datetime.datetime.now()
# Total time:
tt = float((et.microsecond-st.microsecond)/1000)
log.debug("Request time: {}ms".format(tt))
# Send server's response to client
# We don't use token object because we might not have a token (failed login)
if glob.gzip == True:
# First, write the gzipped response
self.write(gzip.compress(responseData, int(glob.conf.config["server"]["gziplevel"])))
# Then, add gzip headers
self.add_header("Vary", "Accept-Encoding")
self.add_header("Content-Encoding", "gzip")
else:
# First, write the response
self.write(responseData)
# Add all the headers AFTER the response has been written
self.set_status(200)
self.add_header("cho-token", responseTokenString)
self.add_header("cho-protocol", "19")
#self.add_header("Keep-Alive", "timeout=5, max=100")
#self.add_header("Connection", "keep-alive")
self.add_header("Content-Type", "text/html; charset=UTF-8")
except:
log.error("Unknown error!\n```\n{}\n{}```".format(sys.exc_info(), traceback.format_exc()))
if glob.sentry:
yield tornado.gen.Task(self.captureException, exc_info=True)
#finally:
# self.finish()
@tornado.web.asynchronous
@tornado.gen.engine
def asyncGet(self):
html = "<html><head><title>MA MAURO ESISTE?</title><style type='text/css'>body{width:30%}</style></head><body><pre>"
html += " _ __<br>"
html += " (_) / /<br>"
html += " ______ __ ____ ____ / /____<br>"
html += " / ___/ / _ \\/ _ \\/ / _ \\<br>"
html += " / / / / /_) / /_) / / ____/<br>"
html += "/__/ /__/ .___/ .___/__/ \\_____/<br>"
html += " / / / /<br>"
html += " /__/ /__/<br>"
html += "<b>PYTHON > ALL VERSION</b><br><br>"
html += "<marquee style='white-space:pre;'><br>"
html += " .. o .<br>"
html += " o.o o . o<br>"
html += " oo...<br>"
html += " __[]__<br>"
html += " phwr--> _\\:D/_/o_o_o_|__ <span style=\"font-family: 'Comic Sans MS'; font-size: 8pt;\">u wot m8</span><br>"
html += " \\\"\"\"\"\"\"\"\"\"\"\"\"\"\"/<br>"
html += " \\ . .. .. . /<br>"
html += "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^<br>"
html += "</marquee><br><strike>reverse engineering a protocol impossible to reverse engineer since always</strike><br>we are actually reverse engineering bancho successfully. for the third time.<br><br><i>&copy; Ripple team, 2016</i></pre></body></html>"
self.write(html)
#yield tornado.gen.Task(self.captureMessage, "test")
#self.finish()

View File

@ -1,266 +0,0 @@
import datetime
import gzip
import sys
import traceback
import tornado.gen
import tornado.web
from raven.contrib.tornado import SentryMixin
from common.log import logUtils as log
from common.web import requestsManager
from constants import exceptions
from constants import packetIDs
from constants import serverPackets
from events import cantSpectateEvent
from events import changeActionEvent
from events import changeMatchModsEvent
from events import changeMatchPasswordEvent
from events import changeMatchSettingsEvent
from events import changeSlotEvent
from events import channelJoinEvent
from events import channelPartEvent
from events import createMatchEvent
from events import friendAddEvent
from events import friendRemoveEvent
from events import joinLobbyEvent
from events import joinMatchEvent
from events import loginEvent
from events import logoutEvent
from events import matchChangeTeamEvent
from events import matchCompleteEvent
from events import matchFailedEvent
from events import matchFramesEvent
from events import matchHasBeatmapEvent
from events import matchInviteEvent
from events import matchLockEvent
from events import matchNoBeatmapEvent
from events import matchPlayerLoadEvent
from events import matchReadyEvent
from events import matchSkipEvent
from events import matchStartEvent
from events import matchTransferHostEvent
from events import partLobbyEvent
from events import partMatchEvent
from events import requestStatusUpdateEvent
from events import sendPrivateMessageEvent
from events import sendPublicMessageEvent
from events import setAwayMessageEvent
from events import spectateFramesEvent
from events import startSpectatingEvent
from events import stopSpectatingEvent
from events import userPanelRequestEvent
from events import userStatsRequestEvent
from events import tournamentMatchInfoRequestEvent
from events import tournamentJoinMatchChannelEvent
from events import tournamentLeaveMatchChannelEvent
from helpers import packetHelper
from objects import glob
from common.sentry import sentry
class handler(requestsManager.asyncRequestHandler):
@tornado.web.asynchronous
@tornado.gen.engine
@sentry.captureTornado
def asyncPost(self):
# Track time if needed
if glob.outputRequestTime:
# Start time
st = datetime.datetime.now()
# Client's token string and request data
requestTokenString = self.request.headers.get("osu-token")
requestData = self.request.body
# Server's token string and request data
responseTokenString = "ayy"
responseData = bytes()
if requestTokenString is None:
# No token, first request. Handle login.
responseTokenString, responseData = loginEvent.handle(self)
else:
userToken = None # default value
try:
# This is not the first packet, send response based on client's request
# Packet start position, used to read stacked packets
pos = 0
# Make sure the token exists
if requestTokenString not in glob.tokens.tokens:
raise exceptions.tokenNotFoundException()
# Token exists, get its object and lock it
userToken = glob.tokens.tokens[requestTokenString]
userToken.processingLock.acquire()
# Keep reading packets until everything has been read
while pos < len(requestData):
# Get packet from stack starting from new packet
leftData = requestData[pos:]
# Get packet ID, data length and data
packetID = packetHelper.readPacketID(leftData)
dataLength = packetHelper.readPacketLength(leftData)
packetData = requestData[pos:(pos+dataLength+7)]
# Console output if needed
if glob.outputPackets and packetID != 4:
log.debug("Incoming packet ({})({}):\n\nPacket code: {}\nPacket length: {}\nSingle packet data: {}\n".format(requestTokenString, userToken.username, str(packetID), str(dataLength), str(packetData)))
# Event handler
def handleEvent(ev):
def wrapper():
ev.handle(userToken, packetData)
return wrapper
eventHandler = {
packetIDs.client_changeAction: handleEvent(changeActionEvent),
packetIDs.client_logout: handleEvent(logoutEvent),
packetIDs.client_friendAdd: handleEvent(friendAddEvent),
packetIDs.client_friendRemove: handleEvent(friendRemoveEvent),
packetIDs.client_userStatsRequest: handleEvent(userStatsRequestEvent),
packetIDs.client_requestStatusUpdate: handleEvent(requestStatusUpdateEvent),
packetIDs.client_userPanelRequest: handleEvent(userPanelRequestEvent),
packetIDs.client_channelJoin: handleEvent(channelJoinEvent),
packetIDs.client_channelPart: handleEvent(channelPartEvent),
packetIDs.client_sendPublicMessage: handleEvent(sendPublicMessageEvent),
packetIDs.client_sendPrivateMessage: handleEvent(sendPrivateMessageEvent),
packetIDs.client_setAwayMessage: handleEvent(setAwayMessageEvent),
packetIDs.client_startSpectating: handleEvent(startSpectatingEvent),
packetIDs.client_stopSpectating: handleEvent(stopSpectatingEvent),
packetIDs.client_cantSpectate: handleEvent(cantSpectateEvent),
packetIDs.client_spectateFrames: handleEvent(spectateFramesEvent),
packetIDs.client_joinLobby: handleEvent(joinLobbyEvent),
packetIDs.client_partLobby: handleEvent(partLobbyEvent),
packetIDs.client_createMatch: handleEvent(createMatchEvent),
packetIDs.client_joinMatch: handleEvent(joinMatchEvent),
packetIDs.client_partMatch: handleEvent(partMatchEvent),
packetIDs.client_matchChangeSlot: handleEvent(changeSlotEvent),
packetIDs.client_matchChangeSettings: handleEvent(changeMatchSettingsEvent),
packetIDs.client_matchChangePassword: handleEvent(changeMatchPasswordEvent),
packetIDs.client_matchChangeMods: handleEvent(changeMatchModsEvent),
packetIDs.client_matchReady: handleEvent(matchReadyEvent),
packetIDs.client_matchNotReady: handleEvent(matchReadyEvent),
packetIDs.client_matchLock: handleEvent(matchLockEvent),
packetIDs.client_matchStart: handleEvent(matchStartEvent),
packetIDs.client_matchLoadComplete: handleEvent(matchPlayerLoadEvent),
packetIDs.client_matchSkipRequest: handleEvent(matchSkipEvent),
packetIDs.client_matchScoreUpdate: handleEvent(matchFramesEvent),
packetIDs.client_matchComplete: handleEvent(matchCompleteEvent),
packetIDs.client_matchNoBeatmap: handleEvent(matchNoBeatmapEvent),
packetIDs.client_matchHasBeatmap: handleEvent(matchHasBeatmapEvent),
packetIDs.client_matchTransferHost: handleEvent(matchTransferHostEvent),
packetIDs.client_matchFailed: handleEvent(matchFailedEvent),
packetIDs.client_matchChangeTeam: handleEvent(matchChangeTeamEvent),
packetIDs.client_invite: handleEvent(matchInviteEvent),
packetIDs.client_tournamentMatchInfoRequest: handleEvent(tournamentMatchInfoRequestEvent),
packetIDs.client_tournamentJoinMatchChannel: handleEvent(tournamentJoinMatchChannelEvent),
packetIDs.client_tournamentLeaveMatchChannel: handleEvent(tournamentLeaveMatchChannelEvent),
}
# Packets processed if in restricted mode.
# All other packets will be ignored if the user is in restricted mode
packetsRestricted = [
packetIDs.client_logout,
packetIDs.client_userStatsRequest,
packetIDs.client_requestStatusUpdate,
packetIDs.client_userPanelRequest,
packetIDs.client_changeAction,
packetIDs.client_channelJoin,
packetIDs.client_channelPart,
]
# Process/ignore packet
if packetID != 4:
if packetID in eventHandler:
if not userToken.restricted or (userToken.restricted and packetID in packetsRestricted):
eventHandler[packetID]()
else:
log.warning("Ignored packet id from {} ({}) (user is restricted)".format(requestTokenString, packetID))
else:
log.warning("Unknown packet id from {} ({})".format(requestTokenString, packetID))
# Update pos so we can read the next stacked packet
# +7 because we add packet ID bytes, unused byte and data length bytes
pos += dataLength+7
# Token queue built, send it
responseTokenString = userToken.token
responseData = userToken.queue
userToken.resetQueue()
except exceptions.tokenNotFoundException:
# Token not found. Disconnect that user
responseData = serverPackets.loginError()
responseData += serverPackets.notification("Whoops! Something went wrong, please login again.")
log.warning("Received packet from unknown token ({}).".format(requestTokenString))
log.info("{} has been disconnected (invalid token)".format(requestTokenString))
finally:
# Unlock token
if userToken is not None:
# Update ping time for timeout
userToken.updatePingTime()
# Release processing lock
userToken.processingLock.release()
# Delete token if kicked
if userToken.kicked:
glob.tokens.deleteToken(userToken)
if glob.outputRequestTime:
# End time
et = datetime.datetime.now()
# Total time:
tt = float((et.microsecond-st.microsecond)/1000)
log.debug("Request time: {}ms".format(tt))
# Send server's response to client
# We don't use token object because we might not have a token (failed login)
if glob.gzip:
# First, write the gzipped response
self.write(gzip.compress(responseData, int(glob.conf.config["server"]["gziplevel"])))
# Then, add gzip headers
self.add_header("Vary", "Accept-Encoding")
self.add_header("Content-Encoding", "gzip")
else:
# First, write the response
self.write(responseData)
# Add all the headers AFTER the response has been written
self.set_status(200)
self.add_header("cho-token", responseTokenString)
self.add_header("cho-protocol", "19")
self.add_header("Connection", "keep-alive")
self.add_header("Keep-Alive", "timeout=5, max=100")
self.add_header("Content-Type", "text/html; charset=UTF-8")
@tornado.web.asynchronous
@tornado.gen.engine
def asyncGet(self):
html = "<html><head><title>MA MAURO ESISTE?</title><style type='text/css'>body{width:30%;background:#222;color:#fff;}</style></head><body><pre>"
html += " _ __<br>"
html += " (_) / /<br>"
html += " ______ __ ____ ____ / /____<br>"
html += " / ___/ / _ \\/ _ \\/ / _ \\<br>"
html += " / / / / /_) / /_) / / ____/<br>"
html += "/__/ /__/ .___/ .___/__/ \\_____/<br>"
html += " / / / /<br>"
html += " /__/ /__/<br>"
html += "<b>PYTHON > ALL VERSION</b><br><br>"
html += "<marquee style='white-space:pre;'><br>"
html += " .. o .<br>"
html += " o.o o . o<br>"
html += " oo...<br>"
html += " __[]__<br>"
html += " phwr--> _\\:D/_/o_o_o_|__ <span style=\"font-family: 'Comic Sans MS'; font-size: 8pt;\">u wot m8</span><br>"
html += " \\\"\"\"\"\"\"\"\"\"\"\"\"\"\"/<br>"
html += " \\ . .. .. . /<br>"
html += "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^<br>"
html += "</marquee><br><strike>reverse engineering a protocol impossible to reverse engineer since always</strike><br>we are actually reverse engineering bancho successfully. for the third time.<br><br>Running osufx branch.<br><i>&copy; Ripple team, 2016</i></pre></body></html>"
self.write(html)

52
handlers/routes.py Normal file
View File

@ -0,0 +1,52 @@
"""
WIP feature that will come in the future.
Don't import
"""
import flask
from objects import glob
from constants import exceptions
@app.route("/api/online-users-count")
def APIonlineUsersCount():
return flask.jsonify({"count" : len(glob.tokens.tokens)-1})
@app.route("/api/user-info")
def APIonlineUsers():
resp = {}
try:
u = flask.request.args.get('u')
# Username/userID
if u.isdigit():
u = int(u)
else:
u = userHelper.getID(u)
if u == None:
raise exceptions.userNotFoundException
# Make sure this user is online
userToken = glob.tokens.getTokenFromUserID(u)
if userToken == None:
raise exceptions.tokenNotFoundException
# Build response dictionary
resp["response"] = "1"
resp[userToken.username] = {
"userID" : userToken.userID,
"actionID" : userToken.actionID,
"actionText" : userToken.actionText,
"actionMd5" : userToken.actionMd5,
"actionMods": userToken.actionMods,
"gameMode": userToken.gameMode,
"country": countryHelper.getCountryLetters(userToken.country),
"position": userToken.location,
"spectating": userToken.spectating,
"spectators": userToken.spectators
}
except exceptions.userNotFoundException:
resp["response"] = "-1"
except exceptions.tokenNotFoundException:
resp["response"] = "-2"
finally:
return flask.jsonify(resp)

View File

@ -1,103 +1,114 @@
from common.log import logUtils as log
from common.ripple import userUtils
from constants import exceptions
from constants import messageTemplates
from constants import serverPackets
from events import logoutEvent
from objects import fokabot
from objects import glob from objects import glob
from helpers import logHelper as log
from constants import exceptions
from constants import serverPackets
from objects import fokabot
from helpers import discordBotHelper
from helpers import userHelper
from events import logoutEvent
from constants import messageTemplates
def joinChannel(userID = 0, channel = "", token = None, toIRC = True):
def joinChannel(userID = 0, channel = "", token = None, toIRC = True, force=False):
""" """
Join a channel Join a channel
:param userID: user ID of the user that joins the channel. Optional. token can be used instead. userID -- user ID of the user that joins the channel. Optional.
:param token: user token object of user that joins the channel. Optional. userID can be used instead. token can be used instead.
:param channel: channel name token -- user token object of user that joins the channel. Optional.
:param toIRC: if True, send this channel join event to IRC. Must be true if joining from bancho. Default: True userID can be used instead.
:param force: whether to allow game clients to join #spect_ and #multi_ channels channel -- name of channe
:return: 0 if joined or other IRC code in case of error. Needed only on IRC-side toIRC -- if True, send this channel join event to IRC. Must be true if joining from bancho.
Optional. Defaukt: True
return -- returns 0 if joined or other IRC code in case of error. Needed only on IRC-side
""" """
try: try:
# Get token if not defined # Get token if not defined
if token is None: if token == None:
token = glob.tokens.getTokenFromUserID(userID) token = glob.tokens.getTokenFromUserID(userID)
# Make sure the token exists # Make sure the token exists
if token is None: if token == None:
raise exceptions.userNotFoundException raise exceptions.userNotFoundException
else: else:
token = token token = token
userID = token.userID
# Get usertoken data
username = token.username
# Normal channel, do check stuff # Normal channel, do check stuff
# Make sure the channel exists # Make sure the channel exists
if channel not in glob.channels.channels: if channel not in glob.channels.channels:
raise exceptions.channelUnknownException() raise exceptions.channelUnknownException
# Make sure a game client is not trying to join a #multi_ or #spect_ channel manually # Check channel permissions
channelObject = glob.channels.channels[channel] channelObject = glob.channels.channels[channel]
if channelObject.isSpecial and not token.irc and not force: if channelObject.publicRead == False and token.admin == False:
raise exceptions.channelUnknownException() raise exceptions.channelNoPermissionsException
# Add our userID to users in that channel
channelObject.userJoin(userID)
# Add the channel to our joined channel # Add the channel to our joined channel
token.joinChannel(channelObject) token.joinChannel(channel)
# Send channel joined (bancho). We use clientName here because of #multiplayer and #spectator channels
token.enqueue(serverPackets.channelJoinSuccess(userID, channelObject.clientName))
# Send channel joined (IRC) # Send channel joined (IRC)
if glob.irc and not toIRC: if glob.irc == True and toIRC == True:
glob.ircServer.banchoJoinChannel(token.username, channel) glob.ircServer.banchoJoinChannel(username, channel)
# Console output # Console output
log.info("{} joined channel {}".format(token.username, channel)) log.info("{} joined channel {}".format(username, channel))
# IRC code return # IRC code return
return 0 return 0
except exceptions.channelNoPermissionsException: except exceptions.channelNoPermissionsException:
log.warning("{} attempted to join channel {}, but they have no read permissions".format(token.username, channel)) log.warning("{} attempted to join channel {}, but they have no read permissions".format(username, channel))
return 403 return 403
except exceptions.channelUnknownException: except exceptions.channelUnknownException:
log.warning("{} attempted to join an unknown channel ({})".format(token.username, channel)) log.warning("{} attempted to join an unknown channel ({})".format(username, channel))
return 403
except exceptions.userAlreadyInChannelException:
log.warning("User {} already in channel {}".format(token.username, channel))
return 403 return 403
except exceptions.userNotFoundException: except exceptions.userNotFoundException:
log.warning("User not connected to IRC/Bancho") log.warning("User not connected to IRC/Bancho")
return 403 # idk return 403 # idk
def partChannel(userID = 0, channel = "", token = None, toIRC = True, kick = False, force=False): def partChannel(userID = 0, channel = "", token = None, toIRC = True, kick = False):
""" """
Part a channel Part a channel
:param userID: user ID of the user that parts the channel. Optional. token can be used instead. userID -- user ID of the user that parts the channel. Optional.
:param token: user token object of user that parts the channel. Optional. userID can be used instead. token can be used instead.
:param channel: channel name token -- user token object of user that parts the channel. Optional.
:param toIRC: if True, send this channel join event to IRC. Must be true if joining from bancho. Optional. Default: True userID can be used instead.
:param kick: if True, channel tab will be closed on client. Used when leaving lobby. Optional. Default: False channel -- name of channel
:param force: whether to allow game clients to part #spect_ and #multi_ channels toIRC -- if True, send this channel join event to IRC. Must be true if joining from bancho.
:return: 0 if joined or other IRC code in case of error. Needed only on IRC-side Optional. Defaukt: True
kick -- if True, channel tab will be closed on client. Used when leaving lobby. Optional. Default: False
return -- returns 0 if joined or other IRC code in case of error. Needed only on IRC-side
""" """
try: try:
# Make sure the client is not drunk and sends partChannel when closing a PM tab
if not channel.startswith("#"):
return
# Get token if not defined # Get token if not defined
if token is None: if token == None:
token = glob.tokens.getTokenFromUserID(userID) token = glob.tokens.getTokenFromUserID(userID)
# Make sure the token exists # Make sure the token exists
if token is None: if token == None:
raise exceptions.userNotFoundException() raise exceptions.userNotFoundException
else: else:
token = token token = token
userID = token.userID
# Get usertoken data
username = token.username
# Determine internal/client name if needed # Determine internal/client name if needed
# (toclient is used clientwise for #multiplayer and #spectator channels) # (toclient is used clientwise for #multiplayer and #spectator channels)
channelClient = channel channelClient = channel
if channel == "#spectator": if channel == "#spectator":
if token.spectating is None: if token.spectating == 0:
s = userID s = userID
else: else:
s = token.spectatingUserID s = token.spectating
channel = "#spect_{}".format(s) channel = "#spect_{}".format(s)
elif channel == "#multiplayer": elif channel == "#multiplayer":
channel = "#multi_{}".format(token.matchID) channel = "#multi_{}".format(token.matchID)
@ -108,96 +119,84 @@ def partChannel(userID = 0, channel = "", token = None, toIRC = True, kick = Fal
# Make sure the channel exists # Make sure the channel exists
if channel not in glob.channels.channels: if channel not in glob.channels.channels:
raise exceptions.channelUnknownException() raise exceptions.channelUnknownException
# Make sure a game client is not trying to join a #multi_ or #spect_ channel manually
channelObject = glob.channels.channels[channel]
if channelObject.isSpecial and not token.irc and not force:
raise exceptions.channelUnknownException()
# Make sure the user is in the channel
if channel not in token.joinedChannels:
raise exceptions.userNotInChannelException()
# Part channel (token-side and channel-side) # Part channel (token-side and channel-side)
token.partChannel(channelObject) channelObject = glob.channels.channels[channel]
token.partChannel(channel)
# Delete temporary channel if everyone left channelObject.userPart(userID)
if "chat/{}".format(channelObject.name) in glob.streams.streams:
if channelObject.temp and len(glob.streams.streams["chat/{}".format(channelObject.name)].clients) - 1 == 0:
glob.channels.removeChannel(channelObject.name)
# Force close tab if needed # Force close tab if needed
# NOTE: Maybe always needed, will check later # NOTE: Maybe always needed, will check later
if kick: if kick == True:
token.enqueue(serverPackets.channelKicked(channelClient)) token.enqueue(serverPackets.channelKicked(channelClient))
# IRC part # IRC part
if glob.irc and toIRC: if glob.irc == True and toIRC == True:
glob.ircServer.banchoPartChannel(token.username, channel) glob.ircServer.banchoPartChannel(username, channel)
# Console output # Console output
log.info("{} parted channel {} ({})".format(token.username, channel, channelClient)) log.info("{} parted channel {} ({})".format(username, channel, channelClient))
# Return IRC code # Return IRC code
return 0 return 0
except exceptions.channelUnknownException: except exceptions.channelUnknownException:
log.warning("{} attempted to part an unknown channel ({})".format(token.username, channel)) log.warning("{} attempted to part an unknown channel ({})".format(username, channel))
return 403 return 403
except exceptions.userNotInChannelException:
log.warning("{} attempted to part {}, but he's not in that channel".format(token.username, channel))
return 442
except exceptions.userNotFoundException: except exceptions.userNotFoundException:
log.warning("User not connected to IRC/Bancho") log.warning("User not connected to IRC/Bancho")
return 442 # idk return 442 # idk
def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True): def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
""" """
Send a message to osu!bancho and IRC server Send a message to osu!bancho and IRC server
:param fro: sender username. Optional. token can be used instead fro -- sender username. Optional.
:param to: receiver channel (if starts with #) or username You can use token instead of this if you wish.
:param message: text of the message to -- receiver channel (if starts with #) or username
:param token: sender token object. Optional. fro can be used instead message -- text of the message
:param toIRC: if True, send the message to IRC. If False, send it to Bancho only. Default: True token -- sender token object.
:return: 0 if joined or other IRC code in case of error. Needed only on IRC-side You can use this instead of fro if you are sending messages from bancho.
Optional.
toIRC -- if True, send the message to IRC. If False, send it to Bancho only.
Optional. Default: True
""" """
try: try:
#tokenString = "" tokenString = ""
# Get token object if not passed # Get token object if not passed
if token is None: if token == None:
token = glob.tokens.getTokenFromUsername(fro) token = glob.tokens.getTokenFromUsername(fro)
if token is None: if token == None:
raise exceptions.userNotFoundException() raise exceptions.userNotFoundException
else: else:
# token object alredy passed, get its string and its username (fro) # token object alredy passed, get its string and its username (fro)
fro = token.username fro = token.username
#tokenString = token.token tokenString = token.token
# Make sure this is not a tournament client # Set some variables
# if token.tournament: userID = token.userID
# raise exceptions.userTournamentException() username = token.username
recipients = []
# Make sure the user is not in restricted mode # Make sure the user is not in restricted mode
if token.restricted: if token.restricted == True:
raise exceptions.userRestrictedException() raise exceptions.userRestrictedException
# Make sure the user is not silenced # Make sure the user is not silenced
if token.isSilenced(): if token.isSilenced() == True:
raise exceptions.userSilencedException() raise exceptions.userSilencedException
# Redirect !report to FokaBot
if message.startswith("!report"):
to = glob.BOT_NAME
# Determine internal name if needed # Determine internal name if needed
# (toclient is used clientwise for #multiplayer and #spectator channels) # (toclient is used clientwise for #multiplayer and #spectator channels)
toClient = to toClient = to
if to == "#spectator": if to == "#spectator":
if token.spectating is None: if token.spectating == 0:
s = token.userID s = userID
else: else:
s = token.spectatingUserID s = token.spectating
to = "#spect_{}".format(s) to = "#spect_{}".format(s)
elif to == "#multiplayer": elif to == "#multiplayer":
to = "#multi_{}".format(token.matchID) to = "#multi_{}".format(token.matchID)
@ -206,10 +205,6 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
elif to.startswith("#multi_"): elif to.startswith("#multi_"):
toClient = "#multiplayer" toClient = "#multiplayer"
# Make sure the message is valid
if not message.strip():
raise exceptions.invalidArgumentsException()
# Truncate message if > 2048 characters # Truncate message if > 2048 characters
message = message[:2048]+"..." if len(message) > 2048 else message message = message[:2048]+"..." if len(message) > 2048 else message
@ -217,185 +212,116 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
message = glob.chatFilters.filterMessage(message) message = glob.chatFilters.filterMessage(message)
# Build packet bytes # Build packet bytes
packet = serverPackets.sendMessage(token.username, toClient, message) packet = serverPackets.sendMessage(username, toClient, message)
# Send the message # Send the message
isChannel = to.startswith("#") isChannel = to.startswith("#")
if isChannel: if isChannel == True:
# CHANNEL # CHANNEL
# Make sure the channel exists # Make sure the channel exists
if to not in glob.channels.channels: if to not in glob.channels.channels:
raise exceptions.channelUnknownException() raise exceptions.channelUnknownException
# Make sure the channel is not in moderated mode # Make sure the channel is not in moderated mode
if glob.channels.channels[to].moderated and not token.admin: if glob.channels.channels[to].moderated == True and token.admin == False:
raise exceptions.channelModeratedException() raise exceptions.channelModeratedException
# Make sure we are in the channel
if to not in token.joinedChannels:
# I'm too lazy to put and test the correct IRC error code here...
# but IRC is not strict at all so who cares
raise exceptions.channelNoPermissionsException()
# Make sure we have write permissions # Make sure we have write permissions
if not glob.channels.channels[to].publicWrite and not token.admin: if glob.channels.channels[to].publicWrite == False and token.admin == False:
raise exceptions.channelNoPermissionsException() raise exceptions.channelNoPermissionsException
# Add message in buffer
token.addMessageInBuffer(to, message)
# Everything seems fine, build recipients list and send packet # Everything seems fine, build recipients list and send packet
glob.streams.broadcast("chat/{}".format(to), packet, but=[token.token]) recipients = glob.channels.channels[to].getConnectedUsers()[:]
for key, value in glob.tokens.tokens.items():
# Skip our client and irc clients
if key == tokenString or value.irc == True:
continue
# Send to this client if it's connected to the channel
if value.userID in recipients:
value.enqueue(packet)
else: else:
# USER # USER
# Make sure recipient user is connected # Make sure recipient user is connected
recipientToken = glob.tokens.getTokenFromUsername(to) recipientToken = glob.tokens.getTokenFromUsername(to)
if recipientToken is None: if recipientToken == None:
raise exceptions.userNotFoundException() raise exceptions.userNotFoundException
# Make sure the recipient is not a tournament client
#if recipientToken.tournament:
# raise exceptions.userTournamentException()
# Make sure the recipient is not restricted or we are FokaBot # Make sure the recipient is not restricted or we are FokaBot
if recipientToken.restricted and fro.lower() != glob.BOT_NAME.lower(): if recipientToken.restricted == True and fro.lower() != "fokabot":
raise exceptions.userRestrictedException() raise exceptions.userRestrictedException
# TODO: Make sure the recipient has not disabled PMs for non-friends or he's our friend # TODO: Make sure the recipient has not disabled PMs for non-friends or he's our friend
# Away check
if recipientToken.awayCheck(token.userID):
sendMessage(to, fro, "\x01ACTION is away: {}\x01".format(recipientToken.awayMessage))
# Check message templates (mods/admins only) # Check message templates (mods/admins only)
if message in messageTemplates.templates and token.admin: if message in messageTemplates.templates and token.admin == True:
sendMessage(fro, to, messageTemplates.templates[message]) sendMessage(fro, to, messageTemplates.templates[message])
# Everything seems fine, send packet # Everything seems fine, send packet
recipientToken.enqueue(packet) recipientToken.enqueue(packet)
# Send the message to IRC # Send the message to IRC
if glob.irc and toIRC: if glob.irc == True and toIRC == True:
messageSplitInLines = message.encode("latin-1").decode("utf-8").split("\n") glob.ircServer.banchoMessage(fro, to, message)
for line in messageSplitInLines:
if line == messageSplitInLines[:1] and line == "":
continue
glob.ircServer.banchoMessage(fro, to, line)
# Spam protection (ignore FokaBot) # Spam protection (ignore FokaBot)
if token.userID > 999: if userID > 999:
token.spamProtection() token.spamProtection()
# Fokabot message # Fokabot message
if isChannel or to.lower() == glob.BOT_NAME.lower(): if isChannel == True or to.lower() == "fokabot":
fokaMessage = fokabot.fokabotResponse(token.username, to, message) fokaMessage = fokabot.fokabotResponse(username, to, message)
if fokaMessage: if fokaMessage != False:
sendMessage(glob.BOT_NAME, to if isChannel else fro, fokaMessage) sendMessage("FokaBot", to if isChannel else fro, fokaMessage)
# File and discord logs (public chat only) # File and discord logs (public chat only)
if to.startswith("#") and not (message.startswith("\x01ACTION is playing") and to.startswith("#spect_")): if to.startswith("#") == True:
log.chat("{fro} @ {to}: {message}".format(fro=token.username, to=to, message=message.encode("latin-1").decode("utf-8"))) log.chat("{fro} @ {to}: {message}".format(fro=username, to=to, message=str(message.encode("utf-8"))))
glob.schiavo.sendChatlog("**{fro} @ {to}:** {message}".format(fro=token.username, to=to, message=message.encode("latin-1").decode("utf-8"))) discordBotHelper.sendChatlog("**{fro} @ {to}:** {message}".format(fro=username, to=to, message=str(message.encode("utf-8"))[2:-1]))
return 0 return 0
except exceptions.userSilencedException: except exceptions.userSilencedException:
token.enqueue(serverPackets.silenceEndTime(token.getSilenceSecondsLeft())) token.enqueue(serverPackets.silenceEndTime(token.getSilenceSecondsLeft()))
log.warning("{} tried to send a message during silence".format(token.username)) log.warning("{} tried to send a message during silence".format(username))
return 404 return 404
except exceptions.channelModeratedException: except exceptions.channelModeratedException:
log.warning("{} tried to send a message to a channel that is in moderated mode ({})".format(token.username, to)) log.warning("{} tried to send a message to a channel that is in moderated mode ({})".format(username, to))
return 404 return 404
except exceptions.channelUnknownException: except exceptions.channelUnknownException:
log.warning("{} tried to send a message to an unknown channel ({})".format(token.username, to)) log.warning("{} tried to send a message to an unknown channel ({})".format(username, to))
return 403 return 403
except exceptions.channelNoPermissionsException: except exceptions.channelNoPermissionsException:
log.warning("{} tried to send a message to channel {}, but they have no write permissions".format(token.username, to)) log.warning("{} tried to send a message to channel {}, but they have no write permissions".format(username, to))
return 404 return 404
except exceptions.userRestrictedException: except exceptions.userRestrictedException:
log.warning("{} tried to send a message {}, but the recipient is in restricted mode".format(token.username, to)) log.warning("{} tried to send a message {}, but the recipient is in restricted mode".format(username, to))
return 404
except exceptions.userTournamentException:
log.warning("{} tried to send a message {}, but the recipient is a tournament client".format(token.username, to))
return 404 return 404
except exceptions.userNotFoundException: except exceptions.userNotFoundException:
log.warning("User not connected to IRC/Bancho") log.warning("User not connected to IRC/Bancho")
return 401 return 401
except exceptions.invalidArgumentsException:
log.warning("{} tried to send an invalid message to {}".format(token.username, to))
return 404
""" IRC-Bancho Connect/Disconnect/Join/Part interfaces""" """ IRC-Bancho Connect/Disconnect/Join/Part interfaces"""
def fixUsernameForBancho(username):
"""
Convert username from IRC format (without spaces) to Bancho format (with spaces)
:param username: username to convert
:return: converted username
"""
# If there are no spaces or underscores in the name
# return it
if " " not in username and "_" not in username:
return username
# Exact match first
result = glob.db.fetch("SELECT id FROM users WHERE username = %s LIMIT 1", [username])
if result is not None:
return username
# Username not found, replace _ with space
return username.replace("_", " ")
def fixUsernameForIRC(username):
"""
Convert an username from Bancho format to IRC format (underscores instead of spaces)
:param username: username to convert
:return: converted username
"""
return username.replace(" ", "_")
def IRCConnect(username): def IRCConnect(username):
""" userID = userHelper.getID(username)
Handle IRC login bancho-side. if userID == False:
Add token and broadcast login packet.
:param username: username
:return:
"""
userID = userUtils.getID(username)
if not userID:
log.warning("{} doesn't exist".format(username)) log.warning("{} doesn't exist".format(username))
return return
glob.tokens.deleteOldTokens(userID) glob.tokens.deleteOldTokens(userID)
glob.tokens.addToken(userID, irc=True) glob.tokens.addToken(userID, irc=True)
glob.streams.broadcast("main", serverPackets.userPanel(userID)) glob.tokens.enqueueAll(serverPackets.userPanel(userID))
log.info("{} logged in from IRC".format(username)) log.info("{} logged in from IRC".format(username))
def IRCDisconnect(username): def IRCDisconnect(username):
"""
Handle IRC logout bancho-side.
Remove token and broadcast logout packet.
:param username: username
:return:
"""
token = glob.tokens.getTokenFromUsername(username) token = glob.tokens.getTokenFromUsername(username)
if token is None: if token == None:
log.warning("{} doesn't exist".format(username)) log.warning("{} doesn't exist".format(username))
return return
logoutEvent.handle(token) logoutEvent.handle(token)
log.info("{} disconnected from IRC".format(username)) log.info("{} disconnected from IRC".format(username))
def IRCJoinChannel(username, channel): def IRCJoinChannel(username, channel):
""" userID = userHelper.getID(username)
Handle IRC channel join bancho-side. if userID == False:
:param username: username
:param channel: channel name
:return: IRC return code
"""
userID = userUtils.getID(username)
if not userID:
log.warning("{} doesn't exist".format(username)) log.warning("{} doesn't exist".format(username))
return return
# NOTE: This should have also `toIRC` = False` tho, # NOTE: This should have also `toIRC` = False` tho,
@ -404,30 +330,8 @@ def IRCJoinChannel(username, channel):
return joinChannel(userID, channel) return joinChannel(userID, channel)
def IRCPartChannel(username, channel): def IRCPartChannel(username, channel):
""" userID = userHelper.getID(username)
Handle IRC channel part bancho-side. if userID == False:
:param username: username
:param channel: channel name
:return: IRC return code
"""
userID = userUtils.getID(username)
if not userID:
log.warning("{} doesn't exist".format(username)) log.warning("{} doesn't exist".format(username))
return return
return partChannel(userID, channel) return partChannel(userID, channel)
def IRCAway(username, message):
"""
Handle IRC away command bancho-side.
:param username:
:param message: away message
:return: IRC return code
"""
userID = userUtils.getID(username)
if not userID:
log.warning("{} doesn't exist".format(username))
return
glob.tokens.getTokenFromUserID(userID).awayMessage = message
return 305 if message == "" else 306

View File

@ -2,16 +2,26 @@ import os
import configparser import configparser
class config: class config:
# Check if config.ini exists and load/generate it """
def __init__(self, file): config.ini object
"""
Initialize a config file object
:param file: file name 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):
""" """
self.config = configparser.ConfigParser() Initialize a config object
self.default = True
self.fileName = file __file -- filename
"""
self.fileName = __file
if os.path.isfile(self.fileName): if os.path.isfile(self.fileName):
# config.ini found, load it # config.ini found, load it
self.config.read(self.fileName) self.config.read(self.fileName)
@ -25,10 +35,11 @@ class config:
# Check if config.ini has all needed the keys # Check if config.ini has all needed the keys
def checkConfig(self): def checkConfig(self):
""" """
Check is the config file has all required keys Check if this config has the required keys
:return: True if valid, False if not valid return -- True if valid, False if not
""" """
try: try:
# Try to get all the required keys # Try to get all the required keys
self.config.get("db","host") self.config.get("db","host")
@ -37,54 +48,38 @@ class config:
self.config.get("db","database") self.config.get("db","database")
self.config.get("db","workers") self.config.get("db","workers")
self.config.get("redis","host")
self.config.get("redis","port")
self.config.get("redis","database")
self.config.get("redis","password")
self.config.get("server","port") self.config.get("server","port")
self.config.get("server","threads")
self.config.get("server","gzip") self.config.get("server","gzip")
self.config.get("server","gziplevel") self.config.get("server","gziplevel")
self.config.get("server","cikey") self.config.get("server","cikey")
self.config.get("server","cloudflare")
self.config.get("cheesegull", "apiurl")
self.config.get("cheesegull", "apikey")
self.config.get("debug","enable") self.config.get("debug","enable")
self.config.get("debug","packets") self.config.get("debug","packets")
self.config.get("debug","time") self.config.get("debug","time")
self.config.get("sentry","enable") self.config.get("sentry","enable")
self.config.get("sentry","banchodsn") self.config.get("sentry","banchodns")
self.config.get("sentry","ircdsn") self.config.get("sentry","ircdns")
self.config.get("discord","enable") self.config.get("discord","enable")
self.config.get("discord","boturl") self.config.get("discord","boturl")
self.config.get("discord","devgroup") self.config.get("discord","devgroup")
self.config.get("datadog", "enable")
self.config.get("datadog", "apikey")
self.config.get("datadog", "appkey")
self.config.get("irc","enable") self.config.get("irc","enable")
self.config.get("irc","port") self.config.get("irc","port")
self.config.get("irc","hostname")
self.config.get("localize","enable") self.config.get("localize","enable")
self.config.get("localize","ipapiurl") self.config.get("localize","ipapiurl")
self.config.get("custom", "config")
return True return True
except configparser.Error: except:
return False return False
def generateDefaultConfig(self):
"""
Write a default config file to disk
:return: # Generate a default config.ini
""" def generateDefaultConfig(self):
"""Open and set default keys for that config file"""
# Open config.ini in write mode # Open config.ini in write mode
f = open(self.fileName, "w") f = open(self.fileName, "w")
@ -96,22 +91,12 @@ class config:
self.config.set("db", "database", "ripple") self.config.set("db", "database", "ripple")
self.config.set("db", "workers", "4") self.config.set("db", "workers", "4")
self.config.add_section("redis")
self.config.set("redis", "host", "localhost")
self.config.set("redis", "port", "6379")
self.config.set("redis", "database", "0")
self.config.set("redis", "password", "")
self.config.add_section("server") self.config.add_section("server")
self.config.set("server", "port", "5001") self.config.set("server", "port", "5001")
self.config.set("server", "threads", "16")
self.config.set("server", "gzip", "1") self.config.set("server", "gzip", "1")
self.config.set("server", "gziplevel", "6") self.config.set("server", "gziplevel", "6")
self.config.set("server", "cikey", "changeme") self.config.set("server", "cikey", "changeme")
self.config.set("server", "cloudflare", "0")
self.config.add_section("cheesegull")
self.config.set("cheesegull", "apiurl", "http://cheesegu.ll/api")
self.config.set("cheesegull", "apikey", "")
self.config.add_section("debug") self.config.add_section("debug")
self.config.set("debug", "enable", "0") self.config.set("debug", "enable", "0")
@ -120,31 +105,22 @@ class config:
self.config.add_section("sentry") self.config.add_section("sentry")
self.config.set("sentry", "enable", "0") self.config.set("sentry", "enable", "0")
self.config.set("sentry", "banchodsn", "") self.config.set("sentry", "banchodns", "")
self.config.set("sentry", "ircdsn", "") self.config.set("sentry", "ircdns", "")
self.config.add_section("discord") self.config.add_section("discord")
self.config.set("discord", "enable", "0") self.config.set("discord", "enable", "0")
self.config.set("discord", "boturl", "") self.config.set("discord", "boturl", "")
self.config.set("discord", "devgroup", "") self.config.set("discord", "devgroup", "")
self.config.add_section("datadog")
self.config.set("datadog", "enable", "0")
self.config.set("datadog", "apikey", "")
self.config.set("datadog", "appkey", "")
self.config.add_section("irc") self.config.add_section("irc")
self.config.set("irc", "enable", "1") self.config.set("irc", "enable", "1")
self.config.set("irc", "port", "6667") self.config.set("irc", "port", "6667")
self.config.set("irc", "hostname", "ripple")
self.config.add_section("localize") self.config.add_section("localize")
self.config.set("localize", "enable", "1") self.config.set("localize", "enable", "1")
self.config.set("localize", "ipapiurl", "http://ip.zxq.co") self.config.set("localize", "ipapiurl", "http://ip.zxq.co")
self.config.add_section("custom")
self.config.set("custom", "config", "common/config.json")
# Write ini to file and close # Write ini to file and close
self.config.write(f) self.config.write(f)
f.close() f.close()

View File

@ -1,14 +1,14 @@
from common.constants import bcolors """Some console related functions"""
from constants import bcolors
from objects import glob from objects import glob
def printServerStartHeader(asciiArt=True): def printServerStartHeader(asciiArt):
""" """Print server start header with optional ascii art
Print server start message
:param asciiArt: print BanchoBoat ascii art. Default: True asciiArt -- if True, will print ascii art too"""
:return:
""" if asciiArt == True:
if asciiArt:
print("{} _ __".format(bcolors.GREEN)) print("{} _ __".format(bcolors.GREEN))
print(" (_) / /") print(" (_) / /")
print(" ______ __ ____ ____ / /____") print(" ______ __ ____ ____ / /____")
@ -27,52 +27,45 @@ def printServerStartHeader(asciiArt=True):
print("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^{}".format(bcolors.ENDC)) print("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^{}".format(bcolors.ENDC))
printColored("> Welcome to pep.py osu!bancho server v{}".format(glob.VERSION), bcolors.GREEN) printColored("> Welcome to pep.py osu!bancho server v{}".format(glob.VERSION), bcolors.GREEN)
printColored("> Common submodule v{}".format(glob.COMMON_VERSION), bcolors.GREEN)
printColored("> Made by the Ripple team", bcolors.GREEN) printColored("> Made by the Ripple team", bcolors.GREEN)
printColored("> {}https://zxq.co/ripple/pep.py".format(bcolors.UNDERLINE), bcolors.GREEN) printColored("> {}https://github.com/osuripple/ripple".format(bcolors.UNDERLINE), bcolors.GREEN)
printColored("> Custom branch by the osufx team (just Sunpy)", bcolors.GREEN) printColored("> Press CTRL+C to exit\n",bcolors.GREEN)
printColored("> {}https://github.com/osufx/pep.py".format(bcolors.UNDERLINE), bcolors.GREEN)
printColored("> Press CTRL+C to exit\n", bcolors.GREEN)
def printNoNl(string): def printNoNl(string):
""" """
Print a string without \n at the end Print string without new line at the end
:param string: string to print string -- string to print
:return:
""" """
print(string, end="") print(string, end="")
def printColored(string, color): def printColored(string, color):
""" """
Print a colored string Print colored string
:param string: string to print string -- string to print
:param color: ANSI color code color -- see bcolors.py
:return:
""" """
print("{}{}{}".format(color, string, bcolors.ENDC)) print("{}{}{}".format(color, string, bcolors.ENDC))
def printError():
"""
Print a red "Error"
:return: def printError():
""" """Print error text FOR LOADING"""
printColored("Error", bcolors.RED) printColored("Error", bcolors.RED)
def printDone():
"""
Print a green "Done"
:return: def printDone():
""" """Print error text FOR LOADING"""
printColored("Done", bcolors.GREEN) printColored("Done", bcolors.GREEN)
def printWarning():
"""
Print a yellow "Warning"
:return: def printWarning():
""" """Print error text FOR LOADING"""
printColored("Warning", bcolors.YELLOW) printColored("Warning", bcolors.YELLOW)

View File

@ -1,4 +1,5 @@
# TODO: Update countries list """Contains all country codes with their osu numeric code"""
countryCodes = { countryCodes = {
"LV": 132, "LV": 132,
"AD": 3, "AD": 3,
@ -252,13 +253,15 @@ countryCodes = {
"AI": 7 "AI": 7
} }
def getCountryID(code): def getCountryID(code):
""" """
Get osu country ID from country letters Get country ID for osu client
:param code: country letters (eg: US) code -- country name abbreviation (eg: US)
:return: country osu code return -- country code int
""" """
if code in countryCodes: if code in countryCodes:
return countryCodes[code] return countryCodes[code]
else: else:
@ -268,9 +271,10 @@ def getCountryLetters(code):
""" """
Get country letters from osu country ID Get country letters from osu country ID
:param code: osu country ID code -- country code int
:return: country letters (XX if not found) return -- country name (2 letters) (XX if code not found)
""" """
for key, value in countryCodes.items(): for key, value in countryCodes.items():
if value == code: if value == code:
return key return key

138
helpers/databaseHelper.py Normal file
View File

@ -0,0 +1,138 @@
import pymysql
from constants import bcolors
from helpers import consoleHelper
import threading
from objects import glob
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
"""
log.debug(query)
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
"""
log.debug(query)
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
log.error("> Reconnected to MySQL server!", bcolors.GREEN)
except:
# Can't ping MySQL server. Show error and call loop in 5 seconds
log.error("[!] 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()

View File

@ -0,0 +1,121 @@
import MySQLdb
import threading
from helpers import logHelper as log
class mysqlWorker:
"""
Instance of a pettirosso meme
"""
def __init__(self, wid, host, username, password, database):
"""
Create a pettirosso meme (mysql worker)
wid -- worker id
host -- hostname
username -- MySQL username
password -- MySQL password
database -- MySQL database name
"""
self.wid = wid
self.connection = MySQLdb.connect(host, username, password, database)
self.connection.autocommit(True)
self.ready = True
self.lock = threading.Lock()
class db:
"""
A MySQL db connection with multiple workers
"""
def __init__(self, host, username, password, database, workers):
"""
Create MySQL workers aka pettirossi meme
host -- hostname
username -- MySQL username
password -- MySQL password
database -- MySQL database name
workers -- Number of workers to spawn
"""
#self.lock = threading.Lock()
#self.connection = MySQLdb.connect(host, username, password, database)
self.workers = []
self.lastWorker = 0
self.workersNumber = workers
for i in range(0,self.workersNumber):
print(".", end="")
self.workers.append(mysqlWorker(i, host, username, password, database))
def getWorker(self):
"""
Return a worker object (round-robin way)
return -- worker object
"""
if self.lastWorker >= self.workersNumber-1:
self.lastWorker = 0
else:
self.lastWorker += 1
#print("Using worker {}".format(self.lastWorker))
return self.workers[self.lastWorker]
def execute(self, query, params = ()):
"""
Executes a query
query -- Query to execute. You can bind parameters with %s
params -- Parameters list. First element replaces first %s and so on. Optional.
"""
log.debug(query)
# Get a worker and acquire its lock
worker = self.getWorker()
worker.lock.acquire()
try:
# Create cursor, execute query and commit
cursor = worker.connection.cursor(MySQLdb.cursors.DictCursor)
cursor.execute(query, params)
return cursor.lastrowid
finally:
# Close the cursor and release worker's lock
if cursor:
cursor.close()
worker.lock.release()
def fetch(self, query, params = (), all = False):
"""
Fetch a single value from db that matches given query
query -- Query to execute. You can bind parameters with %s
params -- Parameters list. First element replaces first %s and so on. Optional.
all -- Fetch one or all values. Used internally. Use fetchAll if you want to fetch all values.
"""
log.debug(query)
# Get a worker and acquire its lock
worker = self.getWorker()
worker.lock.acquire()
try:
# Create cursor, execute the query and fetch one/all result(s)
cursor = worker.connection.cursor(MySQLdb.cursors.DictCursor)
cursor.execute(query, params)
if all == True:
return cursor.fetchall()
else:
return cursor.fetchone()
finally:
# Close the cursor and release worker's lock
if cursor:
cursor.close()
worker.lock.release()
def fetchAll(self, query, params = ()):
"""
Fetch all values from db that matche given query.
Calls self.fetch with all = True.
query -- Query to execute. You can bind parameters with %s
params -- Parameters list. First element replaces first %s and so on. Optional.
"""
return self.fetch(query, params, True)

View File

@ -0,0 +1,69 @@
import requests
from objects import glob
from helpers import generalFunctions
from urllib.parse import urlencode
from helpers import consoleHelper
from constants import bcolors
def sendDiscordMessage(channel, message, alertDev = False, prefix = "**pep.py**"):
"""
Send a message to a discord server.
This is used with ripple's schiavobot.
channel -- bunk, staff or general
message -- message to send
alertDev -- if True, hl developers group
prefix -- string to prepend to message
"""
if glob.discord == True:
for _ in range(0,20):
try:
finalMsg = "{prefix} {message}".format(prefix=prefix, message=message) if alertDev == False else "{prefix} {hl} - {message}".format(prefix=prefix, hl=glob.conf.config["discord"]["devgroup"], message=message)
requests.get("{}/{}?{}".format(glob.conf.config["discord"]["boturl"], channel, urlencode({ "message": finalMsg })))
break
except:
continue
def sendConfidential(message, alertDev = False):
"""
Send a message to #bunker
message -- message to send
"""
sendDiscordMessage("bunk", message, alertDev)
def sendStaff(message):
"""
Send a message to #staff
message -- message to send
"""
sendDiscordMessage("staff", message)
def sendGeneral(message):
"""
Send a message to #general
message -- message to send
"""
sendDiscordMessage("general", message)
def sendChatlog(message):
"""
Send a message to #chatlog
message -- message to send
"""
sendDiscordMessage("chatlog", message, prefix="")
def sendCM(message):
"""
Send a message to #communitymanagers
message -- message to send
"""
sendDiscordMessage("cm", message)

128
helpers/generalFunctions.py Normal file
View File

@ -0,0 +1,128 @@
"""Some functions that don't fit in any other file"""
from constants import mods
from time import gmtime, strftime
def stringToBool(s):
"""
Convert a string (True/true/1) to bool
s -- string/int value
return -- True/False
"""
return (s == "True" or s== "true" or s == "1" or s == 1)
def hexString(s):
"""
Output s' bytes in HEX
s -- string
return -- string with hex value
"""
return ":".join("{:02x}".format(ord(str(c))) for c in s)
def readableMods(__mods):
"""
Return a string with readable std mods.
Used to convert a mods number for oppai
__mods -- mods bitwise number
return -- readable mods string, eg HDDT
"""
r = ""
if __mods == 0:
return r
if __mods & mods.NoFail > 0:
r += "NF"
if __mods & mods.Easy > 0:
r += "EZ"
if __mods & mods.Hidden > 0:
r += "HD"
if __mods & mods.HardRock > 0:
r += "HR"
if __mods & mods.DoubleTime > 0:
r += "DT"
if __mods & mods.HalfTime > 0:
r += "HT"
if __mods & mods.Flashlight > 0:
r += "FL"
if __mods & mods.SpunOut > 0:
r += "SO"
return r
def getRank(gameMode, __mods, acc, c300, c100, c50, cmiss):
"""
Return a string with rank/grade for a given score.
Used mainly for "tillerino"
gameMode -- mode (0 = osu!, 1 = Taiko, 2 = CtB, 3 = osu!mania)
__mods -- mods bitwise number
acc -- accuracy
c300 -- 300 hit count
c100 -- 100 hit count
c50 -- 50 hit count
cmiss -- miss count
return -- rank/grade string
"""
total = c300 + c100 + c50 + cmiss
hdfl = (__mods & (mods.Hidden | mods.Flashlight | mods.FadeIn)) > 0
ss = "sshd" if hdfl else "ss"
s = "shd" if hdfl else "s"
if gameMode == 0 or gameMode == 1:
# osu!std / taiko
ratio300 = c300 / total
ratio50 = c50 / total
if ratio300 == 1:
return ss
if ratio300 > 0.9 and ratio50 <= 0.01 and cmiss == 0:
return s
if (ratio300 > 0.8 and cmiss == 0) or (ratio300 > 0.9):
return "a"
if (ratio300 > 0.7 and cmiss == 0) or (ratio300 > 0.8):
return "b"
if ratio300 > 0.6:
return "c"
return "d"
elif gameMode == 2:
# CtB
if acc == 100:
return ss
if acc > 98:
return s
if acc > 94:
return "a"
if acc > 90:
return "b"
if acc > 85:
return "c"
return "d"
elif gameMode == 3:
# osu!mania
if acc == 100:
return ss
if acc > 95:
return s
if acc > 90:
return "a"
if acc > 80:
return "b"
if acc > 70:
return "c"
return "d"
return "a"
def strContains(s, w):
return (' ' + w + ' ') in (' ' + s + ' ')
def getTimestamp():
"""
Return current time in YYYY-MM-DD HH:MM:SS format.
Used in logs.
"""
return strftime("%Y-%m-%d %H:%M:%S", gmtime())

View File

@ -1,17 +1,17 @@
import json
import urllib.request import urllib.request
import json
from common.log import logUtils as log
from objects import glob from objects import glob
from helpers import logHelper as log
def getCountry(ip): def getCountry(ip):
""" """
Get country from IP address using geoip api Get country from IP address
:param ip: IP address ip -- IP Address
:return: country code. XX if invalid. return -- Country code (2 letters)
""" """
try: try:
# Try to get country from Pikolo Aul's Go-Sanic ip API # Try to get country from Pikolo Aul's Go-Sanic ip API
result = json.loads(urllib.request.urlopen("{}/{}".format(glob.conf.config["localize"]["ipapiurl"], ip), timeout=3).read().decode())["country"] result = json.loads(urllib.request.urlopen("{}/{}".format(glob.conf.config["localize"]["ipapiurl"], ip), timeout=3).read().decode())["country"]
@ -20,17 +20,19 @@ def getCountry(ip):
log.error("Error in get country") log.error("Error in get country")
return "XX" return "XX"
def getLocation(ip): def getLocation(ip):
""" """
Get latitude and longitude from IP address using geoip api Get latitude and longitude from IP address
:param ip: IP address ip -- IP address
:return: (latitude, longitude) return -- [latitude, longitude]
""" """
try: try:
# Try to get position from Pikolo Aul's Go-Sanic ip API # Try to get position from Pikolo Aul's Go-Sanic ip API
result = json.loads(urllib.request.urlopen("{}/{}".format(glob.conf.config["localize"]["ipapiurl"], ip), timeout=3).read().decode())["loc"].split(",") result = json.loads(urllib.request.urlopen("{}/{}".format(glob.conf.config["localize"]["ipapiurl"], ip), timeout=3).read().decode())["loc"].split(",")
return float(result[0]), float(result[1]) return [float(result[0]), float(result[1])]
except: except:
log.error("Error in get position") log.error("Error in get position")
return 0, 0 return [0,0]

140
helpers/logHelper.py Normal file
View File

@ -0,0 +1,140 @@
from constants import bcolors
from helpers import discordBotHelper
from helpers import generalFunctions
from objects import glob
from helpers import userHelper
import time
import os
ENDL = "\n" if os.name == "posix" else "\r\n"
def logMessage(message, alertType = "INFO", messageColor = bcolors.ENDC, discord = None, alertDev = False, of = None, stdout = True):
"""
Logs a message to stdout/discord/file
message -- message to log
alertType -- can be any string. Standard types: INFO, WARNING and ERRORS. Defalt: INFO
messageColor -- message color (see constants.bcolors). Default = bcolots.ENDC (no color)
discord -- discord channel (bunker/cm/staff/general). Optional. Default = None
alertDev -- if True, devs will receive an hl on discord. Default: False
of -- if not None but a string, log the message to that file (inside .data folder). Eg: "warnings.txt" Default: None (don't log to file)
stdout -- if True, print the message to stdout. Default: True
"""
# Get type color from alertType
if alertType == "INFO":
typeColor = bcolors.GREEN
elif alertType == "WARNING":
typeColor = bcolors.YELLOW
elif alertType == "ERROR":
typeColor = bcolors.RED
elif alertType == "CHAT":
typeColor = bcolors.BLUE
elif alertType == "DEBUG":
typeColor = bcolors.PINK
else:
typeColor = bcolors.ENDC
# Message without colors
finalMessage = "[{time}] {type} - {message}".format(time=generalFunctions.getTimestamp(), type=alertType, message=message)
# Message with colors
finalMessageConsole = "{typeColor}[{time}] {type}{endc} - {messageColor}{message}{endc}".format(
time=generalFunctions.getTimestamp(),
type=alertType,
message=message,
typeColor=typeColor,
messageColor=messageColor,
endc=bcolors.ENDC)
# Log to console
if stdout == True:
print(finalMessageConsole)
# Log to discord if needed
if discord != None:
if discord == "bunker":
discordBotHelper.sendConfidential(message, alertDev)
elif discord == "cm":
discordBotHelper.sendCM(message)
elif discord == "staff":
discordBotHelper.sendStaff(message)
elif discord == "general":
discordBotHelper.sendGeneral(message)
# Log to file if needed
if of != None:
try:
glob.fLocks.lockFile(of)
with open(".data/{}".format(of), "a") as f:
f.write(finalMessage+ENDL)
finally:
glob.fLocks.unlockFile(of)
def warning(message, discord = None, alertDev = False):
"""
Log a warning to stdout, warnings.log (always) and discord (optional)
message -- warning message
discord -- if not None, send message to that discord channel through schiavo. Optional. Default = None
alertDev -- if True, send al hl to devs on discord. Optional. Default = False.
"""
logMessage(message, "WARNING", bcolors.YELLOW, discord, alertDev, "warnings.txt")
def error(message, discord = None, alertDev = True):
"""
Log an error to stdout, errors.log (always) and discord (optional)
message -- error message
discord -- if not None, send message to that discord channel through schiavo. Optional. Default = None
alertDev -- if True, send al hl to devs on discord. Optional. Default = False.
"""
logMessage(message, "ERROR", bcolors.RED, discord, alertDev, "errors.txt")
def info(message, discord = None, alertDev = False):
"""
Log an error to stdout (and info.log)
message -- info message
discord -- if not None, send message to that discord channel through schiavo. Optional. Default = None
alertDev -- if True, send al hl to devs on discord. Optional. Default = False.
"""
logMessage(message, "INFO", bcolors.ENDC, discord, alertDev, "info.txt")
def debug(message):
"""
Log a debug message to stdout and debug.log if server is running in debug mode
message -- debug message
"""
if glob.debug == True:
logMessage(message, "DEBUG", bcolors.PINK, of="debug.txt")
def chat(message):
"""
Log public messages to stdout and chatlog_public.txt
message -- chat message
"""
logMessage(message, "CHAT", bcolors.BLUE, of="chatlog_public.txt")
def pm(message):
"""
Log private messages to stdout and chatlog_private.txt
message -- chat message
"""
logMessage(message, "CHAT", bcolors.BLUE, of="chatlog_private.txt")
def rap(userID, message, discord=False, through="FokaBot"):
"""
Log a private message to Admin logs
userID -- userID of who made the action
message -- message without subject (eg: "is a meme" becomes "user is a meme")
discord -- if True, send message to discord
through -- "through" thing string. Optional. Default: "FokaBot"
"""
glob.db.execute("INSERT INTO rap_logs (id, userid, text, datetime, through) VALUES (NULL, %s, %s, %s, %s)", [userID, message, int(time.time()), through])
if discord == True:
username = userHelper.getUsername(userID)
logMessage("{} {}".format(username, message), discord=True)

272
helpers/packetHelper.py Normal file
View File

@ -0,0 +1,272 @@
import struct
from constants import dataTypes
def uleb128Encode(num):
"""
Encode int -> uleb128
num -- int to encode
return -- bytearray with encoded number
"""
arr = bytearray()
length = 0
if num == 0:
return bytearray(b"\x00")
while num > 0:
arr.append(num & 127)
num = num >> 7
if num != 0:
arr[length] = arr[length] | 128
length+=1
return arr
def uleb128Decode(num):
"""
Decode uleb128 -> int
num -- encoded uleb128
return -- list. [total, length]
"""
shift = 0
arr = [0,0] #total, length
while True:
b = num[arr[1]]
arr[1]+=1
arr[0] = arr[0] | (int(b & 127) << shift)
if b & 128 == 0:
break
shift += 7
return arr
def unpackData(__data, __dataType):
"""
Unpacks data according to dataType
__data -- bytes array to unpack
__dataType -- data type. See dataTypes.py
return -- unpacked bytes
"""
# Get right pack Type
if __dataType == dataTypes.uInt16:
unpackType = "<H"
elif __dataType == dataTypes.sInt16:
unpackType = "<h"
elif __dataType == dataTypes.uInt32:
unpackType = "<L"
elif __dataType == dataTypes.sInt32:
unpackType = "<l"
elif __dataType == dataTypes.uInt64:
unpackType = "<Q"
elif __dataType == dataTypes.sInt64:
unpackType = "<q"
elif __dataType == dataTypes.string:
unpackType = "<s"
elif __dataType == dataTypes.ffloat:
unpackType = "<f"
else:
unpackType = "<B"
# Unpack
return struct.unpack(unpackType, bytes(__data))[0]
def packData(__data, __dataType):
"""
Packs data according to dataType
data -- bytes to pack
dataType -- data type. See dataTypes.py
return -- packed bytes
"""
data = bytes() # data to return
pack = True # if True, use pack. False only with strings
# Get right pack Type
if __dataType == dataTypes.bbytes:
# Bytes, do not use pack, do manually
pack = False
data = __data
elif __dataType == dataTypes.intList:
# Pack manually
pack = False
# Add length
data = packData(len(__data), dataTypes.uInt16)
# Add all elements
for i in __data:
data += packData(i, dataTypes.sInt32)
elif __dataType == dataTypes.string:
# String, do not use pack, do manually
pack = False
if len(__data) == 0:
# Empty string
data += b"\x00"
else:
# Non empty string
data += b"\x0B"
data += uleb128Encode(len(__data))
data += str.encode(__data, "latin_1", "ignore")
elif __dataType == dataTypes.uInt16:
packType = "<H"
elif __dataType == dataTypes.sInt16:
packType = "<h"
elif __dataType == dataTypes.uInt32:
packType = "<L"
elif __dataType == dataTypes.sInt32:
packType = "<l"
elif __dataType == dataTypes.uInt64:
packType = "<Q"
elif __dataType == dataTypes.sInt64:
packType = "<q"
elif __dataType == dataTypes.string:
packType = "<s"
elif __dataType == dataTypes.ffloat:
packType = "<f"
else:
packType = "<B"
# Pack if needed
if pack == True:
data += struct.pack(packType, __data)
return data
# TODO: Wat dangerous
def buildPacket(__packet, __packetData = []):
"""
Build a packet
packet -- packet id (int)
packetData -- list [[data, dataType], [data, dataType], ...]
return -- packet bytes
"""
# Set some variables
packetData = bytes()
packetLength = 0
packetBytes = bytes()
# Pack packet data
for i in __packetData:
packetData += packData(i[0], i[1])
# Set packet length
packetLength = len(packetData)
# Return packet as bytes
packetBytes += struct.pack("<h", __packet) # packet id (int16)
packetBytes += bytes(b"\x00") # unused byte
packetBytes += struct.pack("<l", packetLength) # packet lenght (iint32)
packetBytes += packetData # packet data
return packetBytes
def readPacketID(stream):
"""
Read packetID from stream (0-1 bytes)
stream -- data stream
return -- packet ID (int)
"""
return unpackData(stream[0:2], dataTypes.uInt16)
def readPacketLength(stream):
"""
Read packet length from stream (3-4-5-6 bytes)
stream -- data stream
return -- packet length (int)
"""
return unpackData(stream[3:7], dataTypes.uInt32)
def readPacketData(stream, structure = [], hasFirstBytes = True):
"""
Read packet data from stream according to structure
stream -- data stream
structure -- [[name, dataType], [name, dataType], ...]
hasFirstBytes -- if True, stream has packetID and length bytes.
if False, stream has only packetData.
Optional. Default: True
return -- dictionary. key: name, value: read data
"""
# Read packet ID (first 2 bytes)
data = {}
# Skip packet ID and packet length if needed
if hasFirstBytes == True:
end = 7
start = 7
else:
end = 0
start = 0
# Read packet
for i in structure:
start = end
unpack = True
if i[1] == dataTypes.intList:
# sInt32 list.
# Unpack manually with for loop
unpack = False
# Read length (uInt16)
length = unpackData(stream[start:start+2], dataTypes.uInt16)
# Read all int inside list
data[i[0]] = []
for j in range(0,length):
data[i[0]].append(unpackData(stream[start+2+(4*j):start+2+(4*(j+1))], dataTypes.sInt32))
# Update end
end = start+2+(4*length)
elif i[1] == dataTypes.string:
# String, don't unpack
unpack = False
# Check empty string
if stream[start] == 0:
# Empty string
data[i[0]] = ""
end = start+1
else:
# Non empty string
# Read length and calculate end
length = uleb128Decode(stream[start+1:])
end = start+length[0]+length[1]+1
# Read bytes
data[i[0]] = ''.join(chr(j) for j in stream[start+1+length[1]:end])
elif i[1] == dataTypes.byte:
end = start+1
elif i[1] == dataTypes.uInt16 or i[1] == dataTypes.sInt16:
end = start+2
elif i[1] == dataTypes.uInt32 or i[1] == dataTypes.sInt32:
end = start+4
elif i[1] == dataTypes.uInt64 or i[1] == dataTypes.sInt64:
end = start+8
# Unpack if needed
if unpack == True:
data[i[0]] = unpackData(stream[start:end], i[1])
return data

View File

@ -1,268 +0,0 @@
import struct
from constants import dataTypes
cpdef bytearray uleb128Encode(int num):
"""
Encode an int to uleb128
:param num: int to encode
:return: bytearray with encoded number
"""
cdef bytearray arr = bytearray()
cdef int length = 0
if num == 0:
return bytearray(b"\x00")
while num > 0:
arr.append(num & 127)
num >>= 7
if num != 0:
arr[length] |= 128
length+=1
return arr
cpdef list uleb128Decode(bytes num):
"""
Decode a uleb128 to int
:param num: encoded uleb128 int
:return: (total, length)
"""
cdef int shift = 0
cdef list arr = [0,0] #total, length
cdef int b
while True:
b = num[arr[1]]
arr[1]+=1
arr[0] |= int(b & 127) << shift
if b & 128 == 0:
break
shift += 7
return arr
cpdef unpackData(bytes data, int dataType):
"""
Unpacks a single section of a packet.
:param data: bytes to unpack
:param dataType: data type
:return: unpacked bytes
"""
# Get right pack Type
if dataType == dataTypes.UINT16:
unpackType = "<H"
elif dataType == dataTypes.SINT16:
unpackType = "<h"
elif dataType == dataTypes.UINT32:
unpackType = "<L"
elif dataType == dataTypes.SINT32:
unpackType = "<l"
elif dataType == dataTypes.UINT64:
unpackType = "<Q"
elif dataType == dataTypes.SINT64:
unpackType = "<q"
elif dataType == dataTypes.STRING:
unpackType = "<s"
elif dataType == dataTypes.FFLOAT:
unpackType = "<f"
else:
unpackType = "<B"
# Unpack
return struct.unpack(unpackType, bytes(data))[0]
cpdef bytes packData(__data, int dataType):
"""
Packs a single section of a packet.
:param __data: data to pack
:param dataType: data type
:return: packed bytes
"""
cdef bytes data = bytes() # data to return
cdef bint pack = True # if True, use pack. False only with strings
cdef str packType
# Get right pack Type
if dataType == dataTypes.BBYTES:
# Bytes, do not use pack, do manually
pack = False
data = __data
elif dataType == dataTypes.INT_LIST:
# Pack manually
pack = False
# Add length
data = packData(len(__data), dataTypes.UINT16)
# Add all elements
for i in __data:
data += packData(i, dataTypes.SINT32)
elif dataType == dataTypes.STRING:
# String, do not use pack, do manually
pack = False
if len(__data) == 0:
# Empty string
data += b"\x00"
else:
# Non empty string
data += b"\x0B"
data += uleb128Encode(len(__data))
data += str.encode(__data, "latin_1", "ignore")
elif dataType == dataTypes.UINT16:
packType = "<H"
elif dataType == dataTypes.SINT16:
packType = "<h"
elif dataType == dataTypes.UINT32:
packType = "<L"
elif dataType == dataTypes.SINT32:
packType = "<l"
elif dataType == dataTypes.UINT64:
packType = "<Q"
elif dataType == dataTypes.SINT64:
packType = "<q"
elif dataType == dataTypes.STRING:
packType = "<s"
elif dataType == dataTypes.FFLOAT:
packType = "<f"
else:
packType = "<B"
# Pack if needed
if pack:
data += struct.pack(packType, __data)
return data
cpdef bytes buildPacket(int __packet, list __packetData = None):
"""
Builds a packet
:param __packet: packet ID
:param __packetData: packet structure [[data, dataType], [data, dataType], ...]
:return: packet bytes
"""
# Default argument
if __packetData is None:
__packetData = []
# Set some variables
cdef bytes packetData = bytes()
cdef int packetLength = 0
cdef bytes packetBytes = bytes()
# Pack packet data
cdef list i
for i in __packetData:
packetData += packData(i[0], i[1])
# Set packet length
packetLength = len(packetData)
# Return packet as bytes
packetBytes += struct.pack("<h", __packet) # packet id (int16)
packetBytes += bytes(b"\x00") # unused byte
packetBytes += struct.pack("<l", packetLength) # packet lenght (iint32)
packetBytes += packetData # packet data
return packetBytes
cpdef int readPacketID(bytes stream):
"""
Read packetID (first two bytes) from a packet
:param stream: packet bytes
:return: packet ID
"""
return unpackData(stream[0:2], dataTypes.UINT16)
cpdef int readPacketLength(bytes stream):
"""
Read packet data length (3:7 bytes) from a packet
:param stream: packet bytes
:return: packet data length
"""
return unpackData(stream[3:7], dataTypes.UINT32)
cpdef readPacketData(bytes stream, list structure=None, bint hasFirstBytes = True):
"""
Read packet data from `stream` according to `structure`
:param stream: packet bytes
:param structure: packet structure: [[name, dataType], [name, dataType], ...]
:param hasFirstBytes: if True, `stream` has packetID and length bytes.
if False, `stream` has only packet data. Default: True
:return: {name: unpackedValue, ...}
"""
# Default list argument
if structure is None:
structure = []
# Read packet ID (first 2 bytes)
cdef dict data = {}
# Skip packet ID and packet length if needed
cdef start, end
if hasFirstBytes:
end = 7
start = 7
else:
end = 0
start = 0
# Read packet
cdef list i
cdef bint unpack
for i in structure:
start = end
unpack = True
if i[1] == dataTypes.INT_LIST:
# sInt32 list.
# Unpack manually with for loop
unpack = False
# Read length (uInt16)
length = unpackData(stream[start:start+2], dataTypes.UINT16)
# Read all int inside list
data[i[0]] = []
for j in range(0,length):
data[i[0]].append(unpackData(stream[start+2+(4*j):start+2+(4*(j+1))], dataTypes.SINT32))
# Update end
end = start+2+(4*length)
elif i[1] == dataTypes.STRING:
# String, don't unpack
unpack = False
# Check empty string
if stream[start] == 0:
# Empty string
data[i[0]] = ""
end = start+1
else:
# Non empty string
# Read length and calculate end
length = uleb128Decode(stream[start+1:])
end = start+length[0]+length[1]+1
# Read bytes
#data[i[0]] = ''.join(chr(j) for j in stream[start+1+length[1]:end])
data[i[0]] = ""
for j in stream[start+1+length[1]:end]:
data[i[0]] += chr(j)
elif i[1] == dataTypes.BYTE:
end = start+1
elif i[1] == dataTypes.UINT16 or i[1] == dataTypes.SINT16:
end = start+2
elif i[1] == dataTypes.UINT32 or i[1] == dataTypes.SINT32:
end = start+4
elif i[1] == dataTypes.UINT64 or i[1] == dataTypes.SINT64:
end = start+8
# Unpack if needed
if unpack:
data[i[0]] = unpackData(stream[start:end], i[1])
return data

36
helpers/passwordHelper.py Normal file
View File

@ -0,0 +1,36 @@
from helpers import cryptHelper
import base64
import bcrypt
def checkOldPassword(password, salt, rightPassword):
"""
Check if password+salt corresponds to rightPassword
password -- input password
salt -- password's salt
rightPassword -- right password
return -- bool
"""
return (rightPassword == cryptHelper.crypt(password, "$2y$"+str(base64.b64decode(salt))))
def checkNewPassword(password, dbPassword):
"""
Check if a password (version 2) is right.
password -- input password
dbPassword -- the password in the database
return -- bool
"""
password = password.encode("utf8")
dbPassword = dbPassword.encode("utf8")
return bcrypt.hashpw(password, dbPassword) == dbPassword
def genBcrypt(password):
"""
Bcrypts a password.
password -- the password to hash.
return -- bytestring
"""
return bcrypt.hashpw(password.encode("utf8"), bcrypt.gensalt(10, b'2a'))

89
helpers/requestHelper.py Normal file
View File

@ -0,0 +1,89 @@
import tornado
import tornado.web
import tornado.gen
from tornado.ioloop import IOLoop
from objects import glob
from raven.contrib.tornado import SentryMixin
from raven.contrib.tornado import AsyncSentryClient
import gevent
class asyncRequestHandler(tornado.web.RequestHandler):
"""
Tornado asynchronous request handler
create a class that extends this one (requestHelper.asyncRequestHandler)
use asyncGet() and asyncPost() instad of get() and post().
Done. I'm not kidding.
"""
@tornado.web.asynchronous
@tornado.gen.engine
def get(self, *args, **kwargs):
try:
yield tornado.gen.Task(runBackground, (self.asyncGet, tuple(args), dict(kwargs)))
except Exception as e:
yield tornado.gen.Task(self.captureException, exc_info=True)
finally:
if not self._finished:
self.finish()
@tornado.web.asynchronous
@tornado.gen.engine
def post(self, *args, **kwargs):
try:
yield tornado.gen.Task(runBackground, (self.asyncPost, tuple(args), dict(kwargs)))
except Exception as e:
yield tornado.gen.Task(self.captureException, exc_info=True)
finally:
if not self._finished:
self.finish()
def asyncGet(self, *args, **kwargs):
self.send_error(405)
self.finish()
def asyncPost(self, *args, **kwargs):
self.send_error(405)
self.finish()
def getRequestIP(self):
realIP = self.request.headers.get("X-Forwarded-For") if glob.cloudflare == True else self.request.headers.get("X-Real-IP")
if realIP != None:
return realIP
return self.request.remote_ip
def runBackground(data, callback):
"""
Run a function in the background.
Used to handle multiple requests at the same time
"""
func, args, kwargs = data
def _callback(result):
IOLoop.instance().add_callback(lambda: callback(result))
#glob.pool.apply_async(func, args, kwargs, _callback)
g = gevent.Greenlet(func, *args, **kwargs)
g.link(_callback)
g.start()
def checkArguments(arguments, requiredArguments):
"""
Check that every requiredArguments elements are in arguments
arguments -- full argument list, from tornado
requiredArguments -- required arguments list es: ["u", "ha"]
handler -- handler string name to print in exception. Optional
return -- True if all arguments are passed, none if not
"""
for i in requiredArguments:
if i not in arguments:
return False
return True
def printArguments(t):
"""
Print passed arguments, for debug purposes
t -- tornado object (self)
"""
print("ARGS::")
for i in t.request.arguments:
print ("{}={}".format(i, t.get_argument(i)))

View File

@ -1,57 +1,41 @@
import math from objects import glob
from constants import serverPackets
import psutil
import os import os
import signal
import sys import sys
import threading import threading
import time import signal
from helpers import logHelper as log
import psutil
from common.constants import bcolors
from common.log import logUtils as log
from constants import serverPackets
from helpers import consoleHelper
from objects import glob
def dispose():
"""
Perform some clean up. Called on shutdown.
:return:
"""
print("> Disposing server... ")
glob.fileBuffers.flushAll()
consoleHelper.printColored("Goodbye!", bcolors.GREEN)
def runningUnderUnix(): def runningUnderUnix():
""" """
Get if the server is running under UNIX or NT Get if the server is running under UNIX or NT
:return: True if running under UNIX, otherwise False return --- True if running under UNIX, otherwise False
""" """
return True if os.name == "posix" else False return True if os.name == "posix" else False
def scheduleShutdown(sendRestartTime, restart, message = "", delay=20):
def scheduleShutdown(sendRestartTime, restart, message = ""):
""" """
Schedule a server shutdown/restart Schedule a server shutdown/restart
:param sendRestartTime: time (seconds) to wait before sending server restart packets to every client sendRestartTime -- time (seconds) to wait before sending server restart packets to every client
:param restart: if True, server will restart. if False, server will shudown restart -- if True, server will restart. if False, server will shudown
:param message: if set, send that message to every client to warn about the shutdown/restart message -- if set, send that message to every client to warn about the shutdown/restart
:param delay: additional restart delay in seconds. Default: 20
:return:
""" """
# Console output # Console output
log.info("Pep.py will {} in {} seconds!".format("restart" if restart else "shutdown", sendRestartTime+delay), "bunker") log.info("Pep.py will {} in {} seconds!".format("restart" if restart else "shutdown", sendRestartTime+20))
log.info("Sending server restart packets in {} seconds...".format(sendRestartTime)) log.info("Sending server restart packets in {} seconds...".format(sendRestartTime))
# Send notification if set # Send notification if set
if message != "": if message != "":
glob.streams.broadcast("main", serverPackets.notification(message)) glob.tokens.enqueueAll(serverPackets.notification(message))
# Schedule server restart packet # Schedule server restart packet
threading.Timer(sendRestartTime, glob.streams.broadcast, ["main", serverPackets.banchoRestart(delay*2*1000)]).start() threading.Timer(sendRestartTime, glob.tokens.enqueueAll, [serverPackets.banchoRestart(50000)]).start()
glob.restarting = True glob.restarting = True
# Restart/shutdown # Restart/shutdown
@ -60,59 +44,44 @@ def scheduleShutdown(sendRestartTime, restart, message = "", delay=20):
else: else:
action = shutdownServer action = shutdownServer
# Schedule actual server shutdown/restart some seconds after server restart packet, so everyone gets it # Schedule actual server shutdown/restart 20 seconds after server restart packet, so everyone gets it
threading.Timer(sendRestartTime+delay, action).start() threading.Timer(sendRestartTime+20, action).start()
def restartServer(): def restartServer():
""" """Restart pep.py script"""
Restart pep.py
:return:
"""
log.info("Restarting pep.py...") log.info("Restarting pep.py...")
dispose()
os.execv(sys.executable, [sys.executable] + sys.argv) os.execv(sys.executable, [sys.executable] + sys.argv)
def shutdownServer():
"""
Shutdown pep.py
:return: def shutdownServer():
""" """Shutdown pep.py"""
log.info("Shutting down pep.py...") log.info("> Shutting down pep.py...")
dispose()
sig = signal.SIGKILL if runningUnderUnix() else signal.CTRL_C_EVENT sig = signal.SIGKILL if runningUnderUnix() else signal.CTRL_C_EVENT
os.kill(os.getpid(), sig) os.kill(os.getpid(), sig)
def getSystemInfo(): def getSystemInfo():
""" """
Get a dictionary with some system/server info Get a dictionary with some system/server info
:return: ["unix", "connectedUsers", "webServer", "cpuUsage", "totalMemory", "usedMemory", "loadAverage"] return -- ["unix", "connectedUsers", "webServer", "cpuUsage", "totalMemory", "usedMemory", "loadAverage"]
""" """
data = {"unix": runningUnderUnix(), "connectedUsers": len(glob.tokens.tokens), "matches": len(glob.matches.matches)}
data = {}
# Get if server is running under unix/nt
data["unix"] = runningUnderUnix()
# General stats # General stats
delta = time.time()-glob.startTime data["connectedUsers"] = len(glob.tokens.tokens)
days = math.floor(delta/86400) data["matches"] = len(glob.matches.matches)
delta -= days*86400
hours = math.floor(delta/3600)
delta -= hours*3600
minutes = math.floor(delta/60)
delta -= minutes*60
seconds = math.floor(delta)
data["uptime"] = "{}d {}h {}m {}s".format(days, hours, minutes, seconds)
data["cpuUsage"] = psutil.cpu_percent() data["cpuUsage"] = psutil.cpu_percent()
memory = psutil.virtual_memory() data["totalMemory"] = "{0:.2f}".format(psutil.virtual_memory()[0]/1074000000)
data["totalMemory"] = "{0:.2f}".format(memory.total/1074000000) data["usedMemory"] = "{0:.2f}".format(psutil.virtual_memory()[3]/1074000000)
data["usedMemory"] = "{0:.2f}".format(memory.active/1074000000)
# Unix only stats # Unix only stats
if data["unix"]: if data["unix"] == True:
data["loadAverage"] = os.getloadavg() data["loadAverage"] = os.getloadavg()
else: else:
data["loadAverage"] = (0,0,0) data["loadAverage"] = (0,0,0)

621
helpers/userHelper.py Normal file
View File

@ -0,0 +1,621 @@
from helpers import passwordHelper
from constants import gameModes
from helpers import generalFunctions
from objects import glob
from helpers import logHelper as log
import time
from constants import privileges
def getID(username):
"""
Get username's user ID
db -- database connection
username -- user
return -- user id or False
"""
# Get user ID from db
userID = glob.db.fetch("SELECT id FROM users WHERE username = %s", [username])
# Make sure the query returned something
if userID == None:
return False
# Return user ID
return userID["id"]
def checkLogin(userID, password):
"""
Check userID's login with specified password
db -- database connection
userID -- user id
password -- plain md5 password
return -- True or False
"""
# Get password data
passwordData = glob.db.fetch("SELECT password_md5, salt, password_version FROM users WHERE id = %s", [userID])
# Make sure the query returned something
if passwordData == None:
return False
# Return valid/invalid based on the password version.
if passwordData["password_version"] == 2:
return passwordHelper.checkNewPassword(password, passwordData["password_md5"])
if passwordData["password_version"] == 1:
ok = passwordHelper.checkOldPassword(password, passwordData["salt"], passwordData["password_md5"])
if not ok: return False
newpass = passwordHelper.genBcrypt(password)
glob.db.execute("UPDATE users SET password_md5=%s, salt='', password_version='2' WHERE id = %s", [newpass, userID])
def exists(userID):
"""
Check if userID exists
userID -- user ID to check
return -- bool
"""
result = glob.db.fetch("SELECT id FROM users WHERE id = %s", [userID])
if result == None:
return False
else:
return True
def getSilenceEnd(userID):
"""
Get userID's **ABSOLUTE** silence end UNIX time
Remember to subtract time.time() to get the actual silence time
userID -- userID
return -- UNIX time
"""
return glob.db.fetch("SELECT silence_end FROM users WHERE id = %s", [userID])["silence_end"]
def silence(userID, seconds, silenceReason, author = 999):
"""
Silence someone
userID -- userID
seconds -- silence length in seconds
silenceReason -- Silence reason shown on website
author -- userID of who silenced the user. Default: 999
"""
# db qurey
silenceEndTime = int(time.time())+seconds
glob.db.execute("UPDATE users SET silence_end = %s, silence_reason = %s WHERE id = %s", [silenceEndTime, silenceReason, userID])
# Loh
targetUsername = getUsername(userID)
# TODO: exists check im drunk rn i need to sleep (stampa piede ubriaco confirmed)
if seconds > 0:
log.rap(author, "has silenced {} for {} seconds for the following reason: \"{}\"".format(targetUsername, seconds, silenceReason), True)
else:
log.rap(author, "has removed {}'s silence".format(targetUsername), True)
def getRankedScore(userID, gameMode):
"""
Get userID's ranked score relative to gameMode
userID -- userID
gameMode -- int value, see gameModes
return -- ranked score
"""
modeForDB = gameModes.getGameModeForDB(gameMode)
return glob.db.fetch("SELECT ranked_score_"+modeForDB+" FROM users_stats WHERE id = %s", [userID])["ranked_score_"+modeForDB]
def getTotalScore(userID, gameMode):
"""
Get userID's total score relative to gameMode
userID -- userID
gameMode -- int value, see gameModes
return -- total score
"""
modeForDB = gameModes.getGameModeForDB(gameMode)
return glob.db.fetch("SELECT total_score_"+modeForDB+" FROM users_stats WHERE id = %s", [userID])["total_score_"+modeForDB]
def getAccuracy(userID, gameMode):
"""
Get userID's average accuracy relative to gameMode
userID -- userID
gameMode -- int value, see gameModes
return -- accuracy
"""
modeForDB = gameModes.getGameModeForDB(gameMode)
return glob.db.fetch("SELECT avg_accuracy_"+modeForDB+" FROM users_stats WHERE id = %s", [userID])["avg_accuracy_"+modeForDB]
def getGameRank(userID, gameMode):
"""
Get userID's **in-game rank** (eg: #1337) relative to gameMode
userID -- userID
gameMode -- int value, see gameModes
return -- game rank
"""
modeForDB = gameModes.getGameModeForDB(gameMode)
result = glob.db.fetch("SELECT position FROM leaderboard_"+modeForDB+" WHERE user = %s", [userID])
if result == None:
return 0
else:
return result["position"]
def getPlaycount(userID, gameMode):
"""
Get userID's playcount relative to gameMode
userID -- userID
gameMode -- int value, see gameModes
return -- playcount
"""
modeForDB = gameModes.getGameModeForDB(gameMode)
return glob.db.fetch("SELECT playcount_"+modeForDB+" FROM users_stats WHERE id = %s", [userID])["playcount_"+modeForDB]
def getUsername(userID):
"""
Get userID's username
userID -- userID
return -- username
"""
return glob.db.fetch("SELECT username FROM users WHERE id = %s", [userID])["username"]
def getFriendList(userID):
"""
Get userID's friendlist
userID -- userID
return -- list with friends userIDs. [0] if no friends.
"""
# Get friends from db
friends = glob.db.fetchAll("SELECT user2 FROM users_relationships WHERE user1 = %s", [userID])
if friends == None or len(friends) == 0:
# We have no friends, return 0 list
return [0]
else:
# Get only friends
friends = [i["user2"] for i in friends]
# Return friend IDs
return friends
def addFriend(userID, friendID):
"""
Add friendID to userID's friend list
userID -- user
friendID -- new friend
"""
# Make sure we aren't adding us to our friends
if userID == friendID:
return
# check user isn't already a friend of ours
if glob.db.fetch("SELECT id FROM users_relationships WHERE user1 = %s AND user2 = %s", [userID, friendID]) != None:
return
# Set new value
glob.db.execute("INSERT INTO users_relationships (user1, user2) VALUES (%s, %s)", [userID, friendID])
def removeFriend(userID, friendID):
"""
Remove friendID from userID's friend list
userID -- user
friendID -- old friend
"""
# Delete user relationship. We don't need to check if the relationship was there, because who gives a shit,
# if they were not friends and they don't want to be anymore, be it. ¯\_(ツ)_/¯
glob.db.execute("DELETE FROM users_relationships WHERE user1 = %s AND user2 = %s", [userID, friendID])
def getCountry(userID):
"""
Get userID's country **(two letters)**.
Use countryHelper.getCountryID with what that function returns
to get osu! country ID relative to that user
userID -- user
return -- country code (two letters)
"""
return glob.db.fetch("SELECT country FROM users_stats WHERE id = %s", [userID])["country"]
def getPP(userID, gameMode):
"""
Get userID's PP relative to gameMode
userID -- user
return -- PP
"""
modeForDB = gameModes.getGameModeForDB(gameMode)
return glob.db.fetch("SELECT pp_{} FROM users_stats WHERE id = %s".format(modeForDB), [userID])["pp_{}".format(modeForDB)]
def setCountry(userID, country):
"""
Set userID's country (two letters)
userID -- userID
country -- country letters
"""
glob.db.execute("UPDATE users_stats SET country = %s WHERE id = %s", [country, userID])
def getShowCountry(userID):
"""
Get userID's show country status
userID -- userID
return -- True if country is shown, False if it's hidden
"""
country = glob.db.fetch("SELECT show_country FROM users_stats WHERE id = %s", [userID])
if country == None:
return False
return generalFunctions.stringToBool(country)
def logIP(userID, ip):
"""
User IP log
USED FOR MULTIACCOUNT DETECTION
"""
glob.db.execute("""INSERT INTO ip_user (userid, ip, occurencies) VALUES (%s, %s, 1)
ON DUPLICATE KEY UPDATE occurencies = occurencies + 1""", [userID, ip])
def saveBanchoSession(userID, ip):
"""
Save userid and ip of this token in bancho_sessions table.
Used to cache logins on LETS requests
"""
log.debug("Saving bancho session ({}::{}) in db".format(userID, ip))
glob.db.execute("INSERT INTO bancho_sessions (id, userid, ip) VALUES (NULL, %s, %s)", [userID, ip])
def deleteBanchoSessions(userID, ip):
"""Delete this bancho session from DB"""
log.debug("Deleting bancho session ({}::{}) from db".format(userID, ip))
try:
glob.db.execute("DELETE FROM bancho_sessions WHERE userid = %s AND ip = %s", [userID, ip])
except:
log.warning("Token for user: {} ip: {} doesn't exist".format(userID, ip))
def is2FAEnabled(userID):
"""Returns True if 2FA is enable for this account"""
result = glob.db.fetch("SELECT id FROM 2fa_telegram WHERE userid = %s LIMIT 1", [userID])
return True if result is not None else False
def check2FA(userID, ip):
"""Returns True if this IP is untrusted"""
if is2FAEnabled(userID) == False:
return False
result = glob.db.fetch("SELECT id FROM ip_user WHERE userid = %s AND ip = %s", [userID, ip])
return True if result is None else False
def getUserStats(userID, gameMode):
"""
Get all user stats relative to gameMode with only two queries
userID --
gameMode -- gameMode number
return -- dictionary with results
"""
modeForDB = gameModes.getGameModeForDB(gameMode)
# Get stats
stats = glob.db.fetch("""SELECT
ranked_score_{gm} AS rankedScore,
avg_accuracy_{gm} AS accuracy,
playcount_{gm} AS playcount,
total_score_{gm} AS totalScore,
pp_{gm} AS pp
FROM users_stats WHERE id = %s LIMIT 1""".format(gm=modeForDB), [userID])
# Get game rank
result = glob.db.fetch("SELECT position FROM leaderboard_{} WHERE user = %s LIMIT 1".format(modeForDB), [userID])
if result == None:
stats["gameRank"] = 0
else:
stats["gameRank"] = result["position"]
# Return stats + game rank
return stats
def isAllowed(userID):
"""
Check if userID is not banned or restricted
userID -- id of the user
return -- True if not banned or restricted, otherwise false.
"""
result = glob.db.fetch("SELECT privileges FROM users WHERE id = %s", [userID])
if result != None:
return (result["privileges"] & privileges.USER_NORMAL) and (result["privileges"] & privileges.USER_PUBLIC)
else:
return False
def isRestricted(userID):
"""
Check if userID is restricted
userID -- id of the user
return -- True if not restricted, otherwise false.
"""
result = glob.db.fetch("SELECT privileges FROM users WHERE id = %s", [userID])
if result != None:
return (result["privileges"] & privileges.USER_NORMAL) and not (result["privileges"] & privileges.USER_PUBLIC)
else:
return False
def isBanned(userID):
"""
Check if userID is banned
userID -- id of the user
return -- True if not banned, otherwise false.
"""
result = glob.db.fetch("SELECT privileges FROM users WHERE id = %s", [userID])
if result != None:
return not (result["privileges"] & 3 > 0)
else:
return True
def ban(userID):
"""
Ban userID
userID -- id of user
"""
banDateTime = int(time.time())
glob.db.execute("UPDATE users SET privileges = privileges & %s, ban_datetime = %s WHERE id = %s", [ ~(privileges.USER_NORMAL | privileges.USER_PUBLIC | privileges.USER_PENDING_VERIFICATION) , banDateTime, userID])
def unban(userID):
"""
Unban userID
userID -- id of user
"""
glob.db.execute("UPDATE users SET privileges = privileges | %s, ban_datetime = 0 WHERE id = %s", [ (privileges.USER_NORMAL | privileges.USER_PUBLIC) , userID])
def restrict(userID):
"""
Put userID in restricted mode
userID -- id of user
"""
banDateTime = int(time.time())
glob.db.execute("UPDATE users SET privileges = privileges & %s, ban_datetime = %s WHERE id = %s", [~privileges.USER_PUBLIC, banDateTime, userID])
def unrestrict(userID):
"""
Remove restricted mode from userID.
Same as unban().
userID -- id of user
"""
unban(userID)
def getPrivileges(userID):
"""
Return privileges for userID
userID -- id of user
return -- privileges number
"""
result = glob.db.fetch("SELECT privileges FROM users WHERE id = %s", [userID])
if result != None:
return result["privileges"]
else:
return 0
def setPrivileges(userID, priv):
"""
Set userID's privileges in db
userID -- id of user
priv -- privileges number
"""
glob.db.execute("UPDATE users SET privileges = %s WHERE id = %s", [priv, userID])
def isInPrivilegeGroup(userID, groupName):
groupPrivileges = glob.db.fetch("SELECT privileges FROM privileges_groups WHERE name = %s", [groupName])
if groupPrivileges == None:
return False
groupPrivileges = groupPrivileges["privileges"]
userToken = glob.tokens.getTokenFromUserID(userID)
if userToken != None:
userPrivileges = userToken.privileges
else:
userPrivileges = getPrivileges(userID)
return (userPrivileges == groupPrivileges) or (userPrivileges == (groupPrivileges | privileges.USER_DONOR))
def appendNotes(userID, notes, addNl = True):
"""
Append "notes" to current userID's "notes for CM"
userID -- id of user
notes -- text to append
addNl -- if True, prepend \n to notes. Optional. Default: True.
"""
if addNl == True:
notes = "\n"+notes
glob.db.execute("UPDATE users SET notes=CONCAT(COALESCE(notes, ''),%s) WHERE id = %s", [notes, userID])
def logHardware(userID, hashes, activation = False):
"""
Hardware log
USED FOR MULTIACCOUNT DETECTION
Peppy's botnet (client data) structure (new line = "|", already split)
[0] osu! version
[1] plain mac addressed, separated by "."
[2] mac addresses hash set
[3] unique ID
[4] disk ID
return -- True if hw is not banned, otherwise false
"""
# Make sure the strings are not empty
for i in hashes[2:5]:
if i == "":
log.warning("Invalid hash set ({}) for user {} in HWID check".format(hashes, userID), "bunk")
return False
# Run some HWID checks on that user if he is not restricted
if isRestricted(userID) == False:
# Get username
username = getUsername(userID)
# Get the list of banned or restricted users that have logged in from this or similar HWID hash set
banned = glob.db.fetchAll("""SELECT users.id as userid, hw_user.occurencies, users.username FROM hw_user
LEFT JOIN users ON users.id = hw_user.userid
WHERE hw_user.userid != %(userid)s
AND (IF(%(mac)s!='b4ec3c4334a0249dae95c284ec5983df', hw_user.mac = %(mac)s, 1) AND hw_user.unique_id = %(uid)s AND hw_user.disk_id = %(diskid)s)
AND (users.privileges & 3 != 3)""", {
"userid": userID,
"mac": hashes[2],
"uid": hashes[3],
"diskid": hashes[4],
})
for i in banned:
# Get the total numbers of logins
total = glob.db.fetch("SELECT COUNT(*) AS count FROM hw_user WHERE userid = %s LIMIT 1", [userID])
# and make sure it is valid
if total == None:
continue
total = total["count"]
# Calculate 10% of total
perc = (total*10)/100
if i["occurencies"] >= perc:
# If the banned user has logged in more than 10% of the times from this user, restrict this user
restrict(userID)
appendNotes(userID, "-- Logged in from HWID ({hwid}) used more than 10% from user {banned} ({bannedUserID}), who is banned/restricted.".format(
hwid=hashes[2:5],
banned=i["username"],
bannedUserID=i["userid"]
))
log.warning("**{user}** ({userID}) has been restricted because he has logged in from HWID _({hwid})_ used more than 10% from banned/restricted user **{banned}** ({bannedUserID}), **possible multiaccount**.".format(
user=username,
userID=userID,
hwid=hashes[2:5],
banned=i["username"],
bannedUserID=i["userid"]
), "cm")
# Update hash set occurencies
glob.db.execute("""
INSERT INTO hw_user (id, userid, mac, unique_id, disk_id, occurencies) VALUES (NULL, %s, %s, %s, %s, 1)
ON DUPLICATE KEY UPDATE occurencies = occurencies + 1
""", [userID, hashes[2], hashes[3], hashes[4]])
# Optionally, set this hash as 'used for activation'
if activation == True:
glob.db.execute("UPDATE hw_user SET activated = 1 WHERE userid = %s AND mac = %s AND unique_id = %s AND disk_id = %s", [userID, hashes[2], hashes[3], hashes[4]])
# Access granted, abbiamo impiegato 3 giorni
# We grant access even in case of login from banned HWID
# because we call restrict() above so there's no need to deny the access.
return True
def resetPendingFlag(userID, success=True):
"""
Remove pending flag from an user.
userID -- ID of the user
success -- if True, set USER_PUBLIC and USER_NORMAL flags too
"""
glob.db.execute("UPDATE users SET privileges = privileges & %s WHERE id = %s LIMIT 1", [~privileges.USER_PENDING_VERIFICATION, userID])
if success == True:
glob.db.execute("UPDATE users SET privileges = privileges | %s WHERE id = %s LIMIT 1", [(privileges.USER_PUBLIC | privileges.USER_NORMAL), userID])
def verifyUser(userID, hashes):
# Check for valid hash set
for i in hashes[2:5]:
if i == "":
log.warning("Invalid hash set ({}) for user {} while verifying the account".format(str(hashes), userID), "bunk")
return False
# Get username
username = getUsername(userID)
# Make sure there are no other accounts activated with this exact mac/unique id/hwid
match = glob.db.fetchAll("SELECT userid FROM hw_user WHERE (IF(%(mac)s != 'b4ec3c4334a0249dae95c284ec5983df', mac = %(mac)s, 1) AND unique_id = %(uid)s AND disk_id = %(diskid)s) AND userid != %(userid)s AND activated = 1 LIMIT 1", {
"mac": hashes[2],
"uid": hashes[3],
"diskid": hashes[4],
"userid": userID
})
if match:
# This is a multiaccount, restrict other account and ban this account
# Get original userID and username (lowest ID)
originalUserID = match[0]["userid"]
originalUsername = getUsername(originalUserID)
# Ban this user and append notes
ban(userID) # this removes the USER_PENDING_VERIFICATION flag too
appendNotes(userID, "-- {}'s multiaccount ({}), found HWID match while verifying account ({})".format(originalUsername, originalUserID, hashes[2:5]))
appendNotes(originalUserID, "-- Has created multiaccount {} ({})".format(username, userID))
# Restrict the original
restrict(originalUserID)
# Discord message
log.warning("User **{originalUsername}** ({originalUserID}) has been restricted because he has created multiaccount **{username}** ({userID}). The multiaccount has been banned.".format(
originalUsername=originalUsername,
originalUserID=originalUserID,
username=username,
userID=userID
), "cm")
# Disallow login
return False
else:
# No matches found, set USER_PUBLIC and USER_NORMAL flags and reset USER_PENDING_VERIFICATION flag
resetPendingFlag(userID)
log.info("User **{}** ({}) has verified his account with hash set _{}_".format(username, userID, hashes[2:5]), "cm")
# Allow login
return True
def hasVerifiedHardware(userID):
"""
userID -- id of the user
return -- True if hwid activation data is in db, otherwise false
"""
data = glob.db.fetch("SELECT id FROM hw_user WHERE userid = %s AND activated = 1 LIMIT 1", [userID])
if data != None:
return True
return False

View File

@ -6,32 +6,32 @@ by Joel Rosdahl, licensed under the GNU GPL 2 License.
Most of the reference code from miniircd was used for the low-level logic. Most of the reference code from miniircd was used for the low-level logic.
The high-level code has been rewritten to make it compatible with pep.py. The high-level code has been rewritten to make it compatible with pep.py.
""" """
import hashlib
import re
import select
import socket
import sys import sys
import time
import traceback import traceback
import socket
import select
import time
import re
import hashlib
from helpers import logHelper as log
from objects import glob
from helpers import chatHelper as chat
import raven import raven
from common.log import logUtils as log
from common.ripple import userUtils
from helpers import chatHelper as chat
from objects import glob
class Client: class Client:
"""
IRC Client object
"""
__linesep_regexp = re.compile(r"\r?\n") __linesep_regexp = re.compile(r"\r?\n")
def __init__(self, server, sock): def __init__(self, server, sock):
""" """
Initialize a Client object Initialize a Client object
:param server: server object server -- server object
:param sock: socket connection object sock -- socket connection object
:return:
""" """
self.__timestamp = time.time() self.__timestamp = time.time()
self.__readbuffer = "" self.__readbuffer = ""
@ -42,10 +42,8 @@ class Client:
self.server = server self.server = server
self.socket = sock self.socket = sock
(self.ip, self.port) = sock.getpeername() (self.ip, self.port) = sock.getpeername()
self.IRCUsername = "" self.username = ""
self.banchoUsername = ""
self.supposedUsername = "" self.supposedUsername = ""
self.supposedUserID = 0
self.joinedChannels = [] self.joinedChannels = []
def messageChannel(self, channel, command, message, includeSelf=False): def messageChannel(self, channel, command, message, includeSelf=False):
@ -59,8 +57,7 @@ class Client:
Add a message (basic string) to client buffer. Add a message (basic string) to client buffer.
This is the lowest possible level. This is the lowest possible level.
:param msg: message to add msg -- message to add
:return:
""" """
self.__writebuffer += msg + "\r\n" self.__writebuffer += msg + "\r\n"
@ -69,7 +66,7 @@ class Client:
""" """
Return this client's write buffer size Return this client's write buffer size
:return: write buffer size return -- write buffer size
""" """
return len(self.__writebuffer) return len(self.__writebuffer)
@ -78,8 +75,7 @@ class Client:
""" """
Add an IRC-like message to client buffer. Add an IRC-like message to client buffer.
:param msg: message (without IRC stuff) msg -- message (without IRC stuff)
:return:
""" """
self.message(":{} {}".format(self.server.host, msg)) self.message(":{} {}".format(self.server.host, msg))
@ -88,14 +84,13 @@ class Client:
""" """
Add an IRC-like message to client buffer with code Add an IRC-like message to client buffer with code
:param code: response code code -- response code
:param message: response message message -- response message
:param nickname: receiver nickname nickname -- receiver nickname
:param channel: optional channel -- optional
:return:
""" """
if nickname == "": if nickname == "":
nickname = self.IRCUsername nickname = self.username
if channel != "": if channel != "":
channel = " "+channel channel = " "+channel
self.reply("{code:03d} {nickname}{channel} :{message}".format(code=code, nickname=nickname, channel=channel, message=message)) self.reply("{code:03d} {nickname}{channel} :{message}".format(code=code, nickname=nickname, channel=channel, message=message))
@ -105,8 +100,7 @@ class Client:
""" """
Add a 403 reply (no such channel) to client buffer. Add a 403 reply (no such channel) to client buffer.
:param channel: channel -- meh
:return:
""" """
self.replyCode(403, "{} :No such channel".format(channel)) self.replyCode(403, "{} :No such channel".format(channel))
@ -115,8 +109,7 @@ class Client:
""" """
Add a 461 reply (not enough parameters) to client buffer Add a 461 reply (not enough parameters) to client buffer
:param command: name of the command that had not enough parameters command -- command that had not enough parameters
:return:
""" """
self.replyCode(403, "{} :Not enough parameters".format(command)) self.replyCode(403, "{} :Not enough parameters".format(command))
@ -125,9 +118,8 @@ class Client:
""" """
Disconnects this client from the IRC server Disconnects this client from the IRC server
:param quitmsg: IRC quit message. Default: 'Client quit' quitmsg -- IRC quit message. Default: 'Client quit'
:param callLogout: if True, call logoutEvent on bancho callLogout -- if True, call logoutEvent on bancho
:return:
""" """
# Send error to client and close socket # Send error to client and close socket
self.message("ERROR :{}".format(quitmsg)) self.message("ERROR :{}".format(quitmsg))
@ -138,16 +130,12 @@ class Client:
self.server.removeClient(self, quitmsg) self.server.removeClient(self, quitmsg)
# Bancho logout # Bancho logout
if callLogout and self.banchoUsername != "": if callLogout == True:
chat.IRCDisconnect(self.IRCUsername) chat.IRCDisconnect(self.username)
def readSocket(self): def readSocket(self):
""" """Read data coming from this client socket"""
Read data coming from this client socket
:return:
"""
try: try:
# Try to read incoming data from socket # Try to read incoming data from socket
data = self.socket.recv(2 ** 10) data = self.socket.recv(2 ** 10)
@ -155,7 +143,7 @@ class Client:
quitmsg = "EOT" quitmsg = "EOT"
except socket.error as x: except socket.error as x:
# Error while reading data, this client will be disconnected # Error while reading data, this client will be disconnected
data = bytes() data = ""
quitmsg = x quitmsg = x
if data: if data:
@ -170,11 +158,7 @@ class Client:
def parseBuffer(self): def parseBuffer(self):
""" """Parse self.__readbuffer, get command, arguments and call its handler"""
Parse self.__readbuffer, get command, arguments and call its handler
:return:
"""
# Get lines from buffer # Get lines from buffer
lines = self.__linesep_regexp.split(self.__readbuffer) lines = self.__linesep_regexp.split(self.__readbuffer)
self.__readbuffer = lines[-1] self.__readbuffer = lines[-1]
@ -211,25 +195,17 @@ class Client:
def writeSocket(self): def writeSocket(self):
""" """Write buffer to socket"""
Write buffer to socket
:return:
"""
try: try:
sent = self.socket.send(self.__writebuffer.encode()) sent = self.socket.send(self.__writebuffer.encode())
log.debug("[IRC] [{}:{}] <- {}".format(self.ip, self.port, self.__writebuffer[:sent])) log.debug("[IRC] [{}:{}] <- {}".format(self.ip, self.port, self.__writebuffer[:sent]))
self.__writebuffer = self.__writebuffer[sent:] self.__writebuffer = self.__writebuffer[sent:]
except socket.error as x: except socket.error as x:
self.disconnect(str(x)) self.disconnect(x)
def checkAlive(self): def checkAlive(self):
""" """Check if this client is still connected"""
Check if this client is still connected.
If the client is dead, disconnect it.
:return:
"""
now = time.time() now = time.time()
if self.__timestamp + 180 < now: if self.__timestamp + 180 < now:
self.disconnect("ping timeout") self.disconnect("ping timeout")
@ -245,19 +221,11 @@ class Client:
def sendLusers(self): def sendLusers(self):
""" """Send lusers response to this client"""
Send lusers response to this client
:return:
"""
self.replyCode(251, "There are {} users and 0 services on 1 server".format(len(glob.tokens.tokens))) self.replyCode(251, "There are {} users and 0 services on 1 server".format(len(glob.tokens.tokens)))
def sendMotd(self): def sendMotd(self):
""" """Send MOTD to this client"""
Send MOTD to this client
:return:
"""
self.replyCode(375, "- {} Message of the day - ".format(self.server.host)) self.replyCode(375, "- {} Message of the day - ".format(self.server.host))
if len(self.server.motd) == 0: if len(self.server.motd) == 0:
self.replyCode(422, "MOTD File is missing") self.replyCode(422, "MOTD File is missing")
@ -282,10 +250,9 @@ class Client:
m = hashlib.md5() m = hashlib.md5()
m.update(arguments[0].encode("utf-8")) m.update(arguments[0].encode("utf-8"))
tokenHash = m.hexdigest() tokenHash = m.hexdigest()
supposedUser = glob.db.fetch("SELECT users.username, users.id FROM users LEFT JOIN irc_tokens ON users.id = irc_tokens.userid WHERE irc_tokens.token = %s LIMIT 1", [tokenHash]) supposedUsername = glob.db.fetch("SELECT users.username FROM users LEFT JOIN irc_tokens ON users.id = irc_tokens.userid WHERE irc_tokens.token = %s LIMIT 1", [tokenHash])
if supposedUser: if supposedUsername:
self.supposedUsername = chat.fixUsernameForIRC(supposedUser["username"]) self.supposedUsername = supposedUsername["username"]
self.supposedUserID = supposedUser["id"]
self.__handleCommand = self.registerHandler self.__handleCommand = self.registerHandler
else: else:
# Wrong IRC Token # Wrong IRC Token
@ -303,36 +270,29 @@ class Client:
nick = arguments[0] nick = arguments[0]
# Make sure this is the first time we set our nickname # Make sure this is the first time we set our nickname
if self.IRCUsername != "": if self.username != "":
self.reply("432 * %s :Erroneous nickname" % nick) self.reply("432 * %s :Erroneous nickname" % nick)
return return
# Make sure the IRC token was correct: # Make sure the IRC token was correct:
# (self.supposedUsername is already fixed for IRC)
if nick.lower() != self.supposedUsername.lower(): if nick.lower() != self.supposedUsername.lower():
self.reply("464 :Password incorrect") self.reply("464 :Password incorrect")
return return
# Make sure that the user is not banned/restricted:
if not userUtils.isAllowed(self.supposedUserID):
self.reply("465 :You're banned")
return
# Make sure we are not connected to Bancho # Make sure we are not connected to Bancho
token = glob.tokens.getTokenFromUsername(chat.fixUsernameForBancho(nick), True) token = glob.tokens.getTokenFromUsername(nick)
if token is not None: if token != None:
self.reply("433 * {} :Nickname is already in use".format(nick)) self.reply("433 * {} :Nickname is already in use".format(nick))
return return
# Everything seems fine, set username (nickname) # Make sure we are not already connected from IRC with that name
self.IRCUsername = nick # username for IRC
self.banchoUsername = chat.fixUsernameForBancho(self.IRCUsername) # username for bancho
# Disconnect other IRC clients from the same user
for _, value in self.server.clients.items(): for _, value in self.server.clients.items():
if value.IRCUsername.lower() == self.IRCUsername.lower() and value != self: if value.username == self.username and value != self:
value.disconnect(quitmsg="Connected from another client") self.reply("433 * {} :Nickname is already in use".format(nick))
return return
# Everything seems fine, set username (nickname)
self.username = nick
elif command == "USER": elif command == "USER":
# Ignore USER command, we use nickname only # Ignore USER command, we use nickname only
return return
@ -345,9 +305,9 @@ class Client:
return return
# If we now have a valid username, connect to bancho and send IRC welcome stuff # If we now have a valid username, connect to bancho and send IRC welcome stuff
if self.IRCUsername != "": if self.username != "":
# Bancho connection # Bancho connection
chat.IRCConnect(self.banchoUsername) chat.IRCConnect(self.username)
# IRC reply # IRC reply
self.replyCode(1, "Welcome to the Internet Relay Network") self.replyCode(1, "Welcome to the Internet Relay Network")
@ -358,30 +318,30 @@ class Client:
self.sendMotd() self.sendMotd()
self.__handleCommand = self.mainHandler self.__handleCommand = self.mainHandler
def quitHandler(self, _, arguments): def quitHandler(self, command, arguments):
"""QUIT command handler""" """QUIT command handler"""
self.disconnect(self.IRCUsername if len(arguments) < 1 else arguments[0]) self.disconnect(self.username if len(arguments) < 1 else arguments[0])
def joinHandler(self, _, arguments): def joinHandler(self, command, arguments):
"""JOIN command handler""" """JOIN command handler"""
if len(arguments) < 1: if len(arguments) < 1:
self.reply461("JOIN") self.reply461("JOIN")
return return
# Get bancho token object # Get bancho token object
token = glob.tokens.getTokenFromUsername(self.banchoUsername) token = glob.tokens.getTokenFromUsername(self.username)
if token is None: if token == None:
return return
# TODO: Part all channels # TODO: Part all channels
if arguments[0] == "0": if arguments[0] == "0":
return
'''for (channelname, channel) in self.channels.items(): '''for (channelname, channel) in self.channels.items():
self.message_channel(channel, "PART", channelname, True) self.message_channel(channel, "PART", channelname, True)
self.channel_log(channel, "left", meta=True) self.channel_log(channel, "left", meta=True)
server.remove_member_from_channel(self, channelname) server.remove_member_from_channel(self, channelname)
self.channels = {} self.channels = {}
return''' return'''
return
# Get channels to join list # Get channels to join list
channels = arguments[0].split(",") channels = arguments[0].split(",")
@ -394,13 +354,13 @@ class Client:
continue continue
# Attempt to join the channel # Attempt to join the channel
response = chat.IRCJoinChannel(self.banchoUsername, channel) response = chat.IRCJoinChannel(self.username, channel)
if response == 0: if response == 0:
# Joined successfully # Joined successfully
self.joinedChannels.append(channel) self.joinedChannels.append(channel)
# Let everyone in this channel know that we've joined # Let everyone in this channel know that we've joined
self.messageChannel(channel, "{} JOIN".format(self.IRCUsername), channel, True) self.messageChannel(channel, "{} JOIN".format(self.username), channel, True)
# Send channel description (topic) # Send channel description (topic)
description = glob.channels.channels[channel].description description = glob.channels.channels[channel].description
@ -410,18 +370,16 @@ class Client:
self.replyCode(332, description, channel=channel) self.replyCode(332, description, channel=channel)
# Build connected users list # Build connected users list
if "chat/{}".format(channel) not in glob.streams.streams: users = glob.channels.channels[channel].getConnectedUsers()[:]
self.reply403(channel)
continue
users = glob.streams.streams["chat/{}".format(channel)].clients
usernames = [] usernames = []
for user in users: for user in users:
if user not in glob.tokens.tokens: token = glob.tokens.getTokenFromUserID(user)
if token == None:
continue continue
usernames.append(chat.fixUsernameForIRC(glob.tokens.tokens[user].username)) usernames.append(token.username)
usernames = " ".join(usernames) usernames = " ".join(usernames)
# Send IRC users list # Send IRC users lis
self.replyCode(353, usernames, channel="= {}".format(channel)) self.replyCode(353, usernames, channel="= {}".format(channel))
self.replyCode(366, "End of NAMES list", channel=channel) self.replyCode(366, "End of NAMES list", channel=channel)
elif response == 403: elif response == 403:
@ -429,15 +387,15 @@ class Client:
self.reply403(channel) self.reply403(channel)
continue continue
def partHandler(self, _, arguments): def partHandler(self, command, arguments):
"""PART command handler""" """PART command handler"""
if len(arguments) < 1: if len(arguments) < 1:
self.reply461("PART") self.reply461("PART")
return return
# Get bancho token object # Get bancho token object
token = glob.tokens.getTokenFromUsername(self.banchoUsername) token = glob.tokens.getTokenFromUsername(self.username)
if token is None: if token == None:
return return
# Get channels to part list # Get channels to part list
@ -451,7 +409,7 @@ class Client:
continue continue
# Attempt to part the channel # Attempt to part the channel
response = chat.IRCPartChannel(self.banchoUsername, channel) response = chat.IRCPartChannel(self.username, channel)
if response == 0: if response == 0:
# No errors, remove channel from joinedChannels # No errors, remove channel from joinedChannels
self.joinedChannels.remove(channel) self.joinedChannels.remove(channel)
@ -471,39 +429,36 @@ class Client:
if len(arguments) == 1: if len(arguments) == 1:
self.replyCode(412, "No text to send") self.replyCode(412, "No text to send")
return return
recipientIRC = arguments[0] recipient = arguments[0]
message = arguments[1] message = arguments[1]
# Send the message to bancho and reply # Send the message to bancho and reply
if not recipientIRC.startswith("#"): response = chat.sendMessage(self.username, recipient, message, toIRC=False)
recipientBancho = chat.fixUsernameForBancho(recipientIRC)
else:
recipientBancho = recipientIRC
response = chat.sendMessage(self.banchoUsername, recipientBancho, message, toIRC=False)
if response == 404: if response == 404:
self.replyCode(404, "Cannot send to channel", channel=recipientIRC) self.replyCode(404, "Cannot send to channel", channel=recipient)
return return
elif response == 403: elif response == 403:
self.replyCode(403, "No such channel", channel=recipientIRC) self.replyCode(403, "No such channel", channel=recipient)
return return
elif response == 401: elif response == 401:
self.replyCode(401, "No such nick/channel", channel=recipientIRC) self.replyCode(401, "No such nick/channel", channel=recipient)
return return
# Send the message to IRC and bancho # Send the message to IRC and bancho
if recipientIRC.startswith("#"): if recipient.startswith("#"):
# Public message (IRC) # Public message (IRC)
if recipientIRC not in glob.channels.channels: if recipient not in glob.channels.channels:
self.replyCode(401, "No such nick/channel", channel=recipientIRC) self.replyCode(401, "No such nick/channel", channel=recipient)
return return
for _, value in self.server.clients.items(): for _, value in self.server.clients.items():
if recipientIRC in value.joinedChannels and value != self: if recipient in value.joinedChannels and value != self:
value.message(":{} PRIVMSG {} :{}".format(self.IRCUsername, recipientIRC, message)) value.message(":{} PRIVMSG {} :{}".format(self.username, recipient, message))
#self.messageChannel(recipient, command, "{} :{}".format(recipient, message))
else: else:
# Private message (IRC) # Private message (IRC)
for _, value in self.server.clients.items(): for _, value in self.server.clients.items():
if value.IRCUsername == recipientIRC: if value.username == recipient:
value.message(":{} PRIVMSG {} :{}".format(self.IRCUsername, recipientIRC, message)) value.message(":{} PRIVMSG {} :{}".format(self.username, recipient, message))
def motdHandler(self, command, arguments): def motdHandler(self, command, arguments):
"""MOTD command handler""" """MOTD command handler"""
@ -513,7 +468,7 @@ class Client:
"""LUSERS command handler""" """LUSERS command handler"""
self.sendLusers() self.sendLusers()
def pingHandler(self, _, arguments): def pingHandler(self, command, arguments):
"""PING command handler""" """PING command handler"""
if len(arguments) < 1: if len(arguments) < 1:
self.replyCode(409, "No origin specified") self.replyCode(409, "No origin specified")
@ -524,17 +479,10 @@ class Client:
"""(fake) PONG command handler""" """(fake) PONG command handler"""
pass pass
def awayHandler(self, _, arguments):
"""AWAY command handler"""
response = chat.IRCAway(self.banchoUsername, " ".join(arguments))
self.replyCode(response, "You are no longer marked as being away" if response == 305 else "You have been marked as being away")
def mainHandler(self, command, arguments): def mainHandler(self, command, arguments):
""" """Handler for post-login commands"""
Handler for post-login commands
"""
handlers = { handlers = {
"AWAY": self.awayHandler, #"AWAY": away_handler,
#"ISON": ison_handler, #"ISON": ison_handler,
"JOIN": self.joinHandler, "JOIN": self.joinHandler,
#"LIST": list_handler, #"LIST": list_handler,
@ -560,35 +508,36 @@ class Client:
self.replyCode(421, "Unknown command ({})".format(command)) self.replyCode(421, "Unknown command ({})".format(command))
class Server: class Server:
def __init__(self, port): def __init__(self, port):
self.host = glob.conf.config["irc"]["hostname"] self.host = socket.getfqdn("127.0.0.1")[:63]
self.port = port self.port = port
self.clients = {} # Socket - - > Client instance. self.clients = {} # Socket --> Client instance.
self.motd = ["Welcome to pep.py's embedded IRC server!", "This is a VERY simple IRC server and it's still in beta.", "Expect things to crash and not work as expected :("] self.motd = ["Welcome to pep.py's embedded IRC server!", "This is a VERY simple IRC server and it's still in beta.", "Expect things to crash and not work as expected :("]
def forceDisconnection(self, username, isBanchoUsername=True): def forceDisconnection(self, username):
""" """
Disconnect someone from IRC if connected Disconnect someone from IRC if connected
:param username: victim username -- victim
:param isBanchoUsername: if True, username is a bancho username, else convert it to a bancho username
:return:
""" """
for _, value in self.clients.items(): for _, value in self.clients.items():
if (value.IRCUsername == username and not isBanchoUsername) or (value.banchoUsername == username and isBanchoUsername): if value.username == username:
value.disconnect(callLogout=False) value.disconnect(callLogout=False)
break # or dictionary changes size during iteration break# or dictionary changes size during iteration
def banchoJoinChannel(self, username, channel): def banchoJoinChannel(self, username, channel):
""" """
Let every IRC client connected to a specific client know that 'username' joined the channel from bancho Let every IRC client connected to a specific client know that 'username' joined the channel from bancho
:param username: username of bancho user username -- username of bancho user
:param channel: joined channel name channel -- joined channel name
:return:
""" """
username = chat.fixUsernameForIRC(username)
for _, value in self.clients.items(): for _, value in self.clients.items():
if channel in value.joinedChannels: if channel in value.joinedChannels:
value.message(":{} JOIN {}".format(username, channel)) value.message(":{} JOIN {}".format(username, channel))
@ -597,11 +546,9 @@ class Server:
""" """
Let every IRC client connected to a specific client know that 'username' parted the channel from bancho Let every IRC client connected to a specific client know that 'username' parted the channel from bancho
:param username: username of bancho user username -- username of bancho user
:param channel: joined channel name channel -- joined channel name
:return:
""" """
username = chat.fixUsernameForIRC(username)
for _, value in self.clients.items(): for _, value in self.clients.items():
if channel in value.joinedChannels: if channel in value.joinedChannels:
value.message(":{} PART {}".format(username, channel)) value.message(":{} PART {}".format(username, channel))
@ -610,45 +557,37 @@ class Server:
""" """
Send a message to IRC when someone sends it from bancho Send a message to IRC when someone sends it from bancho
:param fro: sender username fro -- sender username
:param to: receiver username to -- receiver username
:param message: text of the message message -- text of the message
:return:
""" """
fro = chat.fixUsernameForIRC(fro)
to = chat.fixUsernameForIRC(to)
if to.startswith("#"): if to.startswith("#"):
# Public message # Public message
for _, value in self.clients.items(): for _, value in self.clients.items():
if to in value.joinedChannels and value.IRCUsername != fro: if to in value.joinedChannels and value.username != fro:
value.message(":{} PRIVMSG {} :{}".format(fro, to, message)) value.message(":{} PRIVMSG {} :{}".format(fro, to, message))
else: else:
# Private message # Private message
for _, value in self.clients.items(): for _, value in self.clients.items():
if value.IRCUsername == to and value.IRCUsername != fro: if value.username == to and value.username != fro:
value.message(":{} PRIVMSG {} :{}".format(fro, to, message)) value.message(":{} PRIVMSG {} :{}".format(fro, to, message))
def removeClient(self, client, _): def removeClient(self, client, quitmsg):
""" """
Remove a client from connected clients Remove a client from connected clients
:param client: client object client -- client object
:return: quitmsg -- QUIT argument, useless atm
""" """
if client.socket in self.clients: if client.socket in self.clients:
del self.clients[client.socket] del self.clients[client.socket]
def start(self): def start(self):
""" """Start IRC server main loop"""
Start IRC server main loop
:return:
"""
# Sentry # Sentry
sentryClient = None if glob.sentry == True:
if glob.sentry: sentryClient = raven.Client(glob.conf.config["sentry"]["ircdns"])
sentryClient = raven.Client(glob.conf.config["sentry"]["ircdsn"])
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
@ -668,7 +607,7 @@ class Server:
[x.socket for x in self.clients.values() [x.socket for x in self.clients.values()
if x.writeBufferSize() > 0], if x.writeBufferSize() > 0],
[], [],
1) 2)
# Handle incoming connections # Handle incoming connections
for x in iwtd: for x in iwtd:
@ -679,7 +618,7 @@ class Server:
try: try:
self.clients[conn] = Client(self, conn) self.clients[conn] = Client(self, conn)
log.info("[IRC] Accepted connection from {}:{}".format(addr[0], addr[1])) log.info("[IRC] Accepted connection from {}:{}".format(addr[0], addr[1]))
except socket.error: except socket.error as e:
try: try:
conn.close() conn.close()
except: except:
@ -698,15 +637,9 @@ class Server:
lastAliveCheck = now lastAliveCheck = now
except: except:
log.error("[IRC] Unknown error!\n```\n{}\n{}```".format(sys.exc_info(), traceback.format_exc())) log.error("[IRC] Unknown error!\n```\n{}\n{}```".format(sys.exc_info(), traceback.format_exc()))
if glob.sentry and sentryClient is not None: if glob.sentry == True:
sentryClient.captureException() sentryClient.captureException()
def main(port=6667): def main(port=6667):
"""
Create and start an IRC server
:param port: IRC port. Default: 6667
:return:
"""
glob.ircServer = Server(port) glob.ircServer = Server(port)
glob.ircServer.start() glob.ircServer.start()

View File

@ -1,9 +1,5 @@
# TODO: Rewrite this shit
from common import generalUtils
from constants import serverPackets
from objects import glob from objects import glob
from common.log import logUtils as log from helpers import generalFunctions
class banchoConfig: class banchoConfig:
""" """
@ -12,56 +8,35 @@ class banchoConfig:
config = {"banchoMaintenance": False, "freeDirect": True, "menuIcon": "", "loginNotification": ""} config = {"banchoMaintenance": False, "freeDirect": True, "menuIcon": "", "loginNotification": ""}
def __init__(self, loadFromDB = True): def __init__(self, __loadFromDB = True):
""" """
Initialize a banchoConfig object (and load bancho_settings from db) Initialize a banchoConfig object (and load bancho_settings from db)
loadFromDB -- if True, load values from db. If False, don't load values. Optional. [__loadFromDB -- if True, load values from db. If False, don't load values. Default: True]
""" """
if loadFromDB:
if __loadFromDB:
try: try:
self.loadSettings() self.loadSettings()
except: except:
raise raise
def loadSettings(self): def loadSettings(self):
""" """
(re)load bancho_settings from DB and set values in config array (re)load bancho_settings from DB and set values in config array
""" """
self.config["banchoMaintenance"] = generalUtils.stringToBool(glob.db.fetch("SELECT value_int FROM bancho_settings WHERE name = 'bancho_maintenance'")["value_int"])
self.config["freeDirect"] = generalUtils.stringToBool(glob.db.fetch("SELECT value_int FROM bancho_settings WHERE name = 'free_direct'")["value_int"]) self.config["banchoMaintenance"] = generalFunctions.stringToBool(glob.db.fetch("SELECT value_int FROM bancho_settings WHERE name = 'bancho_maintenance'")["value_int"])
mainMenuIcon = glob.db.fetch("SELECT file_id, url FROM main_menu_icons WHERE is_current = 1 LIMIT 1") self.config["freeDirect"] = generalFunctions.stringToBool(glob.db.fetch("SELECT value_int FROM bancho_settings WHERE name = 'free_direct'")["value_int"])
if mainMenuIcon is None: self.config["menuIcon"] = glob.db.fetch("SELECT value_string FROM bancho_settings WHERE name = 'menu_icon'")["value_string"]
self.config["menuIcon"] = ""
else:
imageURL = "https://i.ppy.sh/{}.png".format(mainMenuIcon["file_id"])
self.config["menuIcon"] = "{}|{}".format(imageURL, mainMenuIcon["url"])
self.config["loginNotification"] = glob.db.fetch("SELECT value_string FROM bancho_settings WHERE name = 'login_notification'")["value_string"] self.config["loginNotification"] = glob.db.fetch("SELECT value_string FROM bancho_settings WHERE name = 'login_notification'")["value_string"]
def setMaintenance(self, __maintenance):
def setMaintenance(self, maintenance):
""" """
Turn on/off bancho maintenance mode. Write new value to db too Turn on/off bancho maintenance mode. Write new value to db too
maintenance -- if True, turn on maintenance mode. If false, turn it off __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 = %s WHERE name = 'bancho_maintenance'", [int(maintenance)])
def reload(self): self.config["banchoMaintenance"] = __maintenance
# Reload settings from bancho_settings glob.db.execute("UPDATE bancho_settings SET value_int = %s WHERE name = 'bancho_maintenance'", [int(__maintenance)])
glob.banchoConf.loadSettings()
# Reload channels too
glob.channels.loadChannels()
# And chat filters
glob.chatFilters.loadFilters()
# Send new channels and new bottom icon to everyone
glob.streams.broadcast("main", serverPackets.mainMenuIcon(glob.banchoConf.config["menuIcon"]))
glob.streams.broadcast("main", serverPackets.channelInfoEnd())
for key, value in glob.channels.channels.items():
if value.publicRead and not value.hidden:
glob.streams.broadcast("main", serverPackets.channelInfo(key))

View File

@ -1,44 +1,79 @@
import logging
from constants import exceptions
from objects import glob from objects import glob
class channel: class channel:
def __init__(self, name, description, publicRead, publicWrite, temp, hidden): """
A chat channel
"""
def __init__(self, __name, __description, __publicRead, __publicWrite, temp, hidden):
""" """
Create a new chat channel object Create a new chat channel object
:param name: channel name __name -- channel name
:param description: channel description __description -- channel description
:param publicRead: if True, this channel can be read by everyone. If False, it can be read only by mods/admins __publicRead -- bool, if true channel can be read by everyone, if false it can be read only by mods/admins
:param publicWrite: same as public read, but regards writing permissions __publicWrite -- bool, same as public read but relative to write permissions
:param temp: if True, this channel will be deleted when there's no one in this channel temp -- if True, channel will be deleted when there's no one in the channel
:param hidden: if True, thic channel won't be shown in channels list hidden -- if True, channel won't be shown in channels list
""" """
self.name = name
self.description = description self.name = __name
self.publicRead = publicRead self.description = __description
self.publicWrite = publicWrite self.publicRead = __publicRead
self.publicWrite = __publicWrite
self.moderated = False self.moderated = False
self.temp = temp self.temp = temp
self.connectedUsers = [999] # Fokabot is always connected to every channels (otherwise it doesn't show up in IRC users list)
self.hidden = hidden self.hidden = hidden
# Make Foka join the channel # Client name (#spectator/#multiplayer)
fokaToken = glob.tokens.getTokenFromUserID(999) self.clientName = self.name
if fokaToken is not None:
try:
fokaToken.joinChannel(self)
except exceptions.userAlreadyInChannelException:
logging.warning("FokaBot has already joined channel {}".format(self.name))
@property
def isSpecial(self):
return any(self.name.startswith(x) for x in ("#spect_", "#multi_"))
@property
def clientName(self):
if self.name.startswith("#spect_"): if self.name.startswith("#spect_"):
return "#spectator" self.clientName = "#spectator"
elif self.name.startswith("#multi_"): elif self.name.startswith("#multi_"):
return "#multiplayer" self.clientName = "#multiplayer"
return self.name
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
"""
if __userID in self.connectedUsers:
self.connectedUsers.remove(__userID)
# Remove temp channels if empty or there's only fokabot connected
l = len(self.connectedUsers)
if self.temp == True and ((l == 0) or (l == 1 and 999 in self.connectedUsers)):
glob.channels.removeChannel(self.name)
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)

View File

@ -1,18 +1,22 @@
from common.log import logUtils as log
from objects import channel
from objects import glob from objects import glob
from helpers import chatHelper as chat from objects import channel
from helpers import logHelper as log
class channelList: class channelList:
def __init__(self): """
self.channels = {} Channel list
channels -- dictionary. key: channel name, value: channel object
"""
channels = {}
def loadChannels(self): def loadChannels(self):
""" """
Load chat channels from db and add them to channels list Load chat channels from db and add them to channels dictionary
:return:
""" """
# Get channels from DB # Get channels from DB
channels = glob.db.fetchAll("SELECT * FROM bancho_channels") channels = glob.db.fetchAll("SELECT * FROM bancho_channels")
@ -23,67 +27,43 @@ class channelList:
publicWrite = True if i["public_write"] == 1 else False publicWrite = True if i["public_write"] == 1 else False
self.addChannel(i["name"], i["description"], publicRead, publicWrite) self.addChannel(i["name"], i["description"], publicRead, publicWrite)
def addChannel(self, name, description, publicRead, publicWrite, temp = False, hidden = False): def addChannel(self, name, description, publicRead, publicWrite, temp = False, hidden = False):
""" """
Add a channel to channels list Add a channel object to channels dictionary
:param name: channel name name -- channel name
:param description: channel description description -- channel description
:param publicRead: if True, this channel can be read by everyone. If False, it can be read only by mods/admins publicRead -- bool, if true channel can be read by everyone, if false it can be read only by mods/admins
:param publicWrite: same as public read, but regards writing permissions publicWrite -- bool, same as public read but relative to write permissions
:param temp: if True, this channel will be deleted when there's no one in this channel temp -- if True, channel will be deleted when there's no one in the channel. Optional. Default = False.
:param hidden: if True, thic channel won't be shown in channels list hidden -- if True, channel will be hidden in channels list. Optional. Default = False.
:return:
""" """
glob.streams.add("chat/{}".format(name))
self.channels[name] = channel.channel(name, description, publicRead, publicWrite, temp, hidden) self.channels[name] = channel.channel(name, description, publicRead, publicWrite, temp, hidden)
log.info("Created channel {}".format(name)) log.info("Created channel {}".format(name))
def addTempChannel(self, name): def addTempChannel(self, name):
""" """
Add a temporary channel (like #spectator or #multiplayer), gets deleted when there's no one in the channel Add a temporary channel (like #spectator or #multiplayer), gets deleted when there's no one in the channel
and it's hidden in channels list and it's hidden in channels list
:param name: channel name name -- channel name
:return: True if the channel was created, otherwise False return -- True if channel was created, False if failed
""" """
if name in self.channels: if name in self.channels:
return False return False
glob.streams.add("chat/{}".format(name))
self.channels[name] = channel.channel(name, "Chat", True, True, True, True) self.channels[name] = channel.channel(name, "Chat", True, True, True, True)
log.info("Created temp channel {}".format(name)) log.info("Created temp channel {}".format(name))
def addHiddenChannel(self, name):
"""
Add a hidden channel. It's like a normal channel and must be deleted manually,
but it's not shown in channels list.
:param name: channel name
:return: True if the channel was created, otherwise False
"""
if name in self.channels:
return False
glob.streams.add("chat/{}".format(name))
self.channels[name] = channel.channel(name, "Chat", True, True, False, True)
log.info("Created hidden channel {}".format(name))
def removeChannel(self, name): def removeChannel(self, name):
""" """
Removes a channel from channels list Removes a channel from channels list
:param name: channel name name -- channel name
:return:
""" """
if name not in self.channels: if name not in self.channels:
log.debug("{} is not in channels list".format(name)) log.debug("{} is not in channels list".format(name))
return return
#glob.streams.broadcast("chat/{}".format(name), serverPackets.channelKicked(name))
stream = glob.streams.getStream("chat/{}".format(name))
if stream is not None:
for token in stream.clients:
if token in glob.tokens.tokens:
chat.partChannel(channel=name, token=glob.tokens.tokens[token], kick=True)
glob.streams.dispose("chat/{}".format(name))
glob.streams.remove("chat/{}".format(name))
self.channels.pop(name) self.channels.pop(name)
log.info("Removed channel {}".format(name)) log.info("Removed channel {}".format(name))

View File

@ -1,20 +1,9 @@
class chatFilters: class chatFilters:
def __init__(self, fileName="filters.txt"): def __init__(self, fileName="filters.txt"):
"""
Initialize chat filters
:param fileName: name of the file containing filters. Default: filters.txt
"""
self.filters = {} self.filters = {}
self.loadFilters(fileName) self.loadFilters(fileName)
def loadFilters(self, fileName="filters.txt"): def loadFilters(self, fileName="filters.txt"):
"""
Load filters from a file
:param fileName: name of the file containing filters. Default: filters.txt
:return:
"""
# Reset chat filters # Reset chat filters
self.filters = {} self.filters = {}
@ -27,28 +16,17 @@ class chatFilters:
for line in data: for line in data:
# Get old/new word and save it in dictionary # Get old/new word and save it in dictionary
lineSplit = line.split("=") lineSplit = line.split("=")
self.filters[lineSplit[0].lower()] = lineSplit[1].replace("\n", "") self.filters[lineSplit[0]] = lineSplit[1].replace("\n", "")
def filterMessage(self, message): def filterMessage(self, message):
"""
Replace forbidden words with filtered ones
:param message: normal message
:return: filtered message
"""
return message
"""
# Split words by spaces # Split words by spaces
messageTemp = message.split(" ") messageTemp = message.split(" ")
# Check each word # Check each word
for word in messageTemp: for word in messageTemp:
lowerWord = word.lower()
# If the word is filtered, replace it # If the word is filtered, replace it
if lowerWord in self.filters: if word in self.filters:
message = message.replace(word, self.filters[lowerWord]) message = message.replace(word, self.filters[word])
# Return filtered message # Return filtered message
return message return message
"""

20
objects/fileLocks.py Normal file
View File

@ -0,0 +1,20 @@
import threading
class fileLocks:
def __init__(self):
# Dictionary containing threading.Lock s
self.locks = {}
def lockFile(self, fileName):
if fileName in self.locks:
# Acquire existing lock
self.locks[fileName].acquire()
else:
# Create new lock and acquire it
self.locks[fileName] = threading.Lock()
self.locks[fileName].acquire()
def unlockFile(self, fileName):
if fileName in self.locks:
# Release lock if it exists
self.locks[fileName].release()

View File

@ -1,54 +1,55 @@
"""FokaBot related functions""" """FokaBot related functions"""
import re from helpers import userHelper
from common import generalUtils
from common.constants import actions
from common.ripple import userUtils
from constants import fokabotCommands
from constants import serverPackets
from objects import glob from objects import glob
from constants import actions
from constants import serverPackets
from constants import fokabotCommands
import re
from helpers import generalFunctions
# Tillerino np regex, compiled only once to increase performance # Tillerino np regex, compiled only once to increase performance
npRegex = re.compile("^https?:\\/\\/osu\\.ppy\\.sh\\/b\\/(\\d*)") npRegex = re.compile("^https?:\\/\\/osu\\.ppy\\.sh\\/b\\/(\\d*)")
def connect(): def connect():
""" """Add FokaBot to connected users and send userpanel/stats packet to everyone"""
Connect FokaBot to Bancho
:return:
"""
glob.BOT_NAME = userUtils.getUsername(999)
token = glob.tokens.addToken(999) token = glob.tokens.addToken(999)
token.actionID = actions.IDLE token.actionID = actions.idle
glob.streams.broadcast("main", serverPackets.userPanel(999)) glob.tokens.enqueueAll(serverPackets.userPanel(999))
glob.streams.broadcast("main", serverPackets.userStats(999)) ####glob.tokens.enqueueAll(serverPackets.userStats(999))
# NOTE: Debug thing to set all users as connected
#users = glob.db.fetchAll("SELECT id FROM users")
#for i in users:
# t = glob.tokens.addToken(i["id"])
# t.actionID = actions.idle
def disconnect(): def disconnect():
""" """Remove FokaBot from connected users"""
Disconnect FokaBot from Bancho
:return:
"""
glob.tokens.deleteToken(glob.tokens.getTokenFromUserID(999)) glob.tokens.deleteToken(glob.tokens.getTokenFromUserID(999))
def fokabotResponse(fro, chan, message): def fokabotResponse(fro, chan, message):
""" """
Check if a message has triggered FokaBot Check if a message has triggered fokabot (and return its response)
:param fro: sender username fro -- sender username (for permissions stuff with admin commands)
:param chan: channel name (or receiver username) chan -- channel name
:param message: chat mesage message -- message
:return: FokaBot's response or False if no response
return -- fokabot's response string or False
""" """
for i in fokabotCommands.commands: for i in fokabotCommands.commands:
# Loop though all commands # Loop though all commands
if re.compile("^{}( (.+)?)?$".format(i["trigger"])).match(message.strip()): #if i["trigger"] in message:
if generalFunctions.strContains(message, i["trigger"]):
# message has triggered a command # message has triggered a command
# Make sure the user has right permissions # Make sure the user has right permissions
if i["privileges"] is not None: if i["privileges"] != None:
# Rank = x # Rank = x
if userUtils.getPrivileges(userUtils.getID(fro)) & i["privileges"] == 0: if userHelper.getPrivileges(userHelper.getID(fro)) & i["privileges"] == 0:
return False return False
# Check argument number # Check argument number
@ -57,10 +58,10 @@ def fokabotResponse(fro, chan, message):
return "Wrong syntax: {} {}".format(i["trigger"], i["syntax"]) return "Wrong syntax: {} {}".format(i["trigger"], i["syntax"])
# Return response or execute callback # Return response or execute callback
if i["callback"] is None: if i["callback"] == None:
return i["response"] return i["response"]
else: else:
return i["callback"](fro, chan, message[1:]) return i["callback"](fro, chan, message[1:])
# No commands triggered # No commands triggered
return False return False

View File

@ -1,59 +1,34 @@
"""Global objects and variables""" """Global objects and variables"""
import time from objects import tokenList
from common.ddog import datadogClient
from common.files import fileBuffer, fileLocks
from objects import channelList from objects import channelList
from objects import matchList from objects import matchList
from objects import streamList from objects import fileLocks
from objects import tokenList
from common.web import schiavo
try: try:
with open("version") as f: with open("version") as f:
VERSION = f.read().strip() VERSION = f.read()
if VERSION == "": if VERSION == "":
raise Exception raise
except: except:
VERSION = "Unknown" VERSION = "¯\_(xd)_/¯"
DATADOG_PREFIX = "peppy"
BOT_NAME = "FokaBot"
application = None
db = None db = None
redis = None
conf = None conf = None
banchoConf = None banchoConf = None
tokens = tokenList.tokenList() tokens = tokenList.tokenList()
channels = channelList.channelList() channels = channelList.channelList()
matches = matchList.matchList() matches = matchList.matchList()
restarting = False
fLocks = fileLocks.fileLocks() fLocks = fileLocks.fileLocks()
fileBuffers = fileBuffer.buffersList()
schiavo = schiavo.schiavo()
dog = datadogClient.datadogClient()
verifiedCache = {} verifiedCache = {}
cloudflare = False
chatFilters = None chatFilters = None
pool = None
ircServer = None
busyThreads = 0
debug = False debug = False
outputRequestTime = False outputRequestTime = False
outputPackets = False outputPackets = False
discord = False
gzip = False gzip = False
localize = False localize = False
sentry = False sentry = False
irc = False
restarting = False
startTime = int(time.time())
streams = streamList.streamList()
# Additional modifications
COMMON_VERSION_REQ = "1.2.1"
try:
with open("common/version") as f:
COMMON_VERSION = f.read().strip()
except:
COMMON_VERSION = "Unknown"

24
objects/logThread.py Normal file
View File

@ -0,0 +1,24 @@
'''
import threading
class task:
def __init__(self, function, args = (), kwargs = {}):
self.function = function
self.args = args
self.kwargs = kwargs
class logThread:
def __init__(self):
self.thread = threading.Thread()
self.queue = []
def enqueue(self, function, args = (), kwargs = {}):
self.queue.append(task(function, args, kwargs))
def run(self):
for i in self.queue:
self.thread = threading.Thread(i.function, i.args, i.kwargs)
self.thread.run()
self.thread.join()
self.queue = []
'''

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More