Compare commits
1 Commits
master
...
privileges
Author | SHA1 | Date | |
---|---|---|---|
|
7b06f2921e |
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -1,10 +1,9 @@
|
||||||
**/__pycache__
|
**/__pycache__
|
||||||
**/build
|
|
||||||
config.ini
|
config.ini
|
||||||
filters.txt
|
filters.txt
|
||||||
.data
|
.data
|
||||||
.idea
|
.idea
|
||||||
|
common_funzia
|
||||||
|
common_refractor
|
||||||
|
common_memato
|
||||||
redistest.py
|
redistest.py
|
||||||
*.c
|
|
||||||
*.so
|
|
||||||
.pyenv
|
|
2
.gitmodules
vendored
2
.gitmodules
vendored
|
@ -1,3 +1,3 @@
|
||||||
[submodule "common"]
|
[submodule "common"]
|
||||||
path = common
|
path = common
|
||||||
url = https://github.com/osufx/ripple-python-common.git
|
url = git@git.zxq.co:ripple/ripple-python-common.git
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
python-targets:
|
|
||||||
- 3
|
|
||||||
pep8:
|
|
||||||
none: true
|
|
||||||
pylint:
|
|
||||||
disable:
|
|
||||||
- cyclic-import
|
|
15
README.md
15
README.md
|
@ -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,8 +9,6 @@ This is Ripple's bancho server. It handles:
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
- Python 3.5
|
- Python 3.5
|
||||||
- Cython
|
|
||||||
- C compiler
|
|
||||||
- MySQLdb (`mysqlclient`)
|
- MySQLdb (`mysqlclient`)
|
||||||
- Tornado
|
- Tornado
|
||||||
- Bcrypt
|
- Bcrypt
|
||||||
|
@ -29,14 +23,9 @@ afterwards, install the required dependencies with pip
|
||||||
```
|
```
|
||||||
$ pip install -r requirements.txt
|
$ pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
then, compile all `*.pyx` files to `*.so` or `*.dll` files using `setup.py` (distutils file)
|
then, run pep.py once to create the default config file and edit it
|
||||||
```
|
|
||||||
$ 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
|
||||||
|
|
2
common
2
common
|
@ -1 +1 @@
|
||||||
Subproject commit 6103fe96a79cd8f5cbabe24b5fac9cf2a5cacb4a
|
Subproject commit cccd6208170f34ab070a15bbbc0fc768bb3bd4df
|
|
@ -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
|
||||||
|
@ -99,7 +100,7 @@ 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
|
||||||
|
@ -143,26 +144,6 @@ def transferHost(stream):
|
||||||
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):
|
def tournamentMatchInfoRequest(stream):
|
||||||
return packetHelper.readPacketData(stream, [["matchID", dataTypes.UINT32]])
|
return packetHelper.readPacketData(stream, [["matchID", dataTypes.UINT32]])
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
"""Bancho exceptions"""
|
||||||
|
# TODO: Prints in exceptions
|
||||||
class loginFailedException(Exception):
|
class loginFailedException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -90,21 +92,3 @@ class unknownStreamException(Exception):
|
||||||
|
|
||||||
class userTournamentException(Exception):
|
class userTournamentException(Exception):
|
||||||
pass
|
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
|
@ -1,2 +1,2 @@
|
||||||
NORMAL = 0
|
normal = 0
|
||||||
FREE_MOD = 1
|
freeMod = 1
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
SCORE = 0
|
score = 0
|
||||||
ACCURACY = 1
|
accuracy = 1
|
||||||
COMBO = 2
|
combo = 2
|
||||||
SCORE_V2 = 3
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
NO_TEAM = 0
|
noTeam = 0
|
||||||
BLUE = 1
|
blue = 1
|
||||||
RED = 2
|
red = 2
|
||||||
|
|
|
@ -78,7 +78,5 @@ server_userSilenced = 94
|
||||||
server_userPresenceBundle = 96
|
server_userPresenceBundle = 96
|
||||||
client_userPanelRequest = 97
|
client_userPanelRequest = 97
|
||||||
client_tournamentMatchInfoRequest = 93
|
client_tournamentMatchInfoRequest = 93
|
||||||
server_matchAbort = 106
|
|
||||||
server_switchServer = 107
|
|
||||||
client_tournamentJoinMatchChannel = 108
|
client_tournamentJoinMatchChannel = 108
|
||||||
client_tournamentLeaveMatchChannel = 109
|
client_tournamentLeaveMatchChannel = 109
|
|
@ -16,12 +16,12 @@ def forceUpdate():
|
||||||
|
|
||||||
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 appeal after one month since your ban by sending an email to support@ripple.moe from the email address you've used to sign up.")
|
||||||
return packets
|
return packets
|
||||||
|
|
||||||
def loginLocked():
|
def loginLocked():
|
||||||
packets = packetHelper.buildPacket(packetIDs.server_userID, [[-1, dataTypes.SINT32]])
|
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"]))
|
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 support@ripple.moe from the email address you've used to sign up.")
|
||||||
return packets
|
return packets
|
||||||
|
|
||||||
def loginError():
|
def loginError():
|
||||||
|
@ -94,12 +94,12 @@ 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
|
userRank = 0
|
||||||
if username == glob.BOT_NAME:
|
if username == "FokaBot":
|
||||||
|
userRank |= userRanks.MOD
|
||||||
|
elif userUtils.isInPrivilegeGroup(userID, "community manager"):
|
||||||
userRank |= userRanks.MOD
|
userRank |= userRanks.MOD
|
||||||
elif userUtils.isInPrivilegeGroup(userID, "developer"):
|
elif userUtils.isInPrivilegeGroup(userID, "developer"):
|
||||||
userRank |= userRanks.ADMIN
|
userRank |= userRanks.ADMIN
|
||||||
elif userUtils.isInPrivilegeGroup(userID, "chat mod"):
|
|
||||||
userRank |= userRanks.MOD
|
|
||||||
elif (userToken.privileges & privileges.USER_DONOR) > 0:
|
elif (userToken.privileges & privileges.USER_DONOR) > 0:
|
||||||
userRank |= userRanks.SUPPORTER
|
userRank |= userRanks.SUPPORTER
|
||||||
else:
|
else:
|
||||||
|
@ -155,13 +155,11 @@ 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, [
|
||||||
[channel.name, dataTypes.STRING],
|
[chan, dataTypes.STRING],
|
||||||
[channel.description, dataTypes.STRING],
|
[channel.description, dataTypes.STRING],
|
||||||
[len(glob.streams.streams["chat/{}".format(chan)].clients), dataTypes.UINT16]
|
[channel.getConnectedUsersCount(), dataTypes.UINT16]
|
||||||
])
|
])
|
||||||
|
|
||||||
def channelInfoEnd():
|
def channelInfoEnd():
|
||||||
|
@ -202,18 +200,17 @@ def createMatch(matchID):
|
||||||
|
|
||||||
# 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
|
# 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 bytes()
|
||||||
|
|
||||||
# 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
|
||||||
|
@ -264,11 +261,6 @@ def playerFailed(slotID):
|
||||||
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):
|
||||||
|
@ -276,6 +268,3 @@ def notification(message):
|
||||||
|
|
||||||
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]])
|
|
|
@ -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
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
"""Bancho user ranks"""
|
||||||
NORMAL = 0
|
NORMAL = 0
|
||||||
PLAYER = 1
|
PLAYER = 1
|
||||||
BAT = 2
|
BAT = 2
|
||||||
|
|
|
@ -1,21 +1,28 @@
|
||||||
|
from common.constants import actions
|
||||||
from common.log import logUtils as log
|
from common.log import logUtils as log
|
||||||
|
from common.ripple import userUtils
|
||||||
from constants import clientPackets
|
from constants import clientPackets
|
||||||
from constants import serverPackets
|
from constants import serverPackets
|
||||||
from objects import glob
|
from objects import glob
|
||||||
|
|
||||||
|
|
||||||
def handle(userToken, packetData):
|
def handle(userToken, packetData):
|
||||||
# Get usertoken data
|
# Get usertoken data
|
||||||
userID = userToken.userID
|
userID = userToken.userID
|
||||||
username = userToken.username
|
username = userToken.username
|
||||||
|
|
||||||
|
# Update privileges
|
||||||
|
userToken.updatePrivileges()
|
||||||
|
|
||||||
# Make sure we are not banned
|
# Make sure we are not banned
|
||||||
#if userUtils.isBanned(userID):
|
if userUtils.isBanned(priv=userToken.privileges):
|
||||||
# userToken.enqueue(serverPackets.loginBanned())
|
userToken.enqueue(serverPackets.loginBanned())
|
||||||
# return
|
return
|
||||||
|
|
||||||
# Send restricted message if needed
|
# Send restricted message if needed
|
||||||
#if userToken.restricted:
|
if not userToken.restricted:
|
||||||
# userToken.checkRestricted(True)
|
if userUtils.isRestricted(priv=userToken.privileges):
|
||||||
|
userToken.setRestricted()
|
||||||
|
|
||||||
# Change action packet
|
# Change action packet
|
||||||
packetData = clientPackets.userActionChange(packetData)
|
packetData = clientPackets.userActionChange(packetData)
|
||||||
|
@ -31,10 +38,8 @@ if userToken.matchID != -1 and userToken.actionID != actions.MULTIPLAYING and us
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# Update cached stats if our pp changed if we've just submitted a score or we've changed gameMode
|
# 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"]):
|
if (userToken.actionID == actions.PLAYING or userToken.actionID == actions.MULTIPLAYING) or (userToken.pp != userUtils.getPP(userID, userToken.gameMode)) or (userToken.gameMode != packetData["gameMode"]):
|
||||||
|
# Always update game mode, or we'll cache stats from the wrong game mode if we've changed it
|
||||||
# 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()
|
||||||
|
|
||||||
|
|
|
@ -15,29 +15,29 @@ 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
|
||||||
# Host can set global DT/HT
|
if userID == match.hostUserID:
|
||||||
if userID == match.hostUserID:
|
# If host has selected DT/HT and Freemod is enabled, set DT/HT as match mod
|
||||||
# If host has selected DT/HT and Freemod is enabled, set DT/HT as match mod
|
if (packetData["mods"] & mods.DOUBLETIME) > 0:
|
||||||
if (packetData["mods"] & mods.DOUBLETIME) > 0:
|
match.changeMods(mods.DOUBLETIME)
|
||||||
match.changeMods(mods.DOUBLETIME)
|
# Nightcore
|
||||||
# Nightcore
|
if (packetData["mods"] & mods.NIGHTCORE) > 0:
|
||||||
if (packetData["mods"] & mods.NIGHTCORE) > 0:
|
match.changeMods(match.mods + mods.NIGHTCORE)
|
||||||
match.changeMods(match.mods + mods.NIGHTCORE)
|
elif (packetData["mods"] & mods.HALFTIME) > 0:
|
||||||
elif (packetData["mods"] & mods.HALFTIME) > 0:
|
match.changeMods(mods.HALFTIME)
|
||||||
match.changeMods(mods.HALFTIME)
|
else:
|
||||||
else:
|
# No DT/HT, set global mods to 0 (we are in freemod mode)
|
||||||
# No DT/HT, set global mods to 0 (we are in freemod mode)
|
match.changeMods(0)
|
||||||
match.changeMods(0)
|
|
||||||
|
|
||||||
# Set slot mods
|
# Set slot mods
|
||||||
slotID = match.getUserSlotID(userID)
|
slotID = match.getUserSlotID(userID)
|
||||||
if slotID is not None:
|
if slotID is not None:
|
||||||
match.setSlotMods(slotID, packetData["mods"])
|
match.setSlotMods(slotID, packetData["mods"])
|
||||||
else:
|
else:
|
||||||
# Not freemod, set match mods
|
# Not freemod, set match mods
|
||||||
match.changeMods(packetData["mods"])
|
match.changeMods(packetData["mods"])
|
||||||
|
|
|
@ -10,10 +10,12 @@ 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
|
# Host check
|
||||||
match.changePassword(packetData["matchPassword"])
|
if userToken.userID != match.hostUserID:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update match password
|
||||||
|
match.changePassword(packetData["matchPassword"])
|
||||||
|
|
|
@ -21,84 +21,97 @@ 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]
|
||||||
|
|
||||||
# Host check
|
# Host check
|
||||||
with glob.matches.matches[matchID] as match:
|
if userToken.userID != match.hostUserID:
|
||||||
if userToken.userID != match.hostUserID:
|
return
|
||||||
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"] != "":
|
if packetData["matchPassword"] != "":
|
||||||
match.matchPassword = generalUtils.stringMd5(packetData["matchPassword"])
|
match.matchPassword = generalUtils.stringMd5(packetData["matchPassword"])
|
||||||
else:
|
else:
|
||||||
match.matchPassword = ""
|
match.matchPassword = ""
|
||||||
match.beatmapName = packetData["beatmapName"]
|
match.beatmapName = packetData["beatmapName"]
|
||||||
match.beatmapID = packetData["beatmapID"]
|
match.beatmapID = packetData["beatmapID"]
|
||||||
match.hostUserID = packetData["hostUserID"]
|
match.hostUserID = packetData["hostUserID"]
|
||||||
match.gameMode = packetData["gameMode"]
|
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.sendUpdates()
|
||||||
|
|
||||||
# Console output
|
# Console output
|
||||||
log.info("MPROOM{}: Updated room settings".format(match.matchID))
|
log.info("MPROOM{}: Updated room settings".format(match.matchID))
|
||||||
|
|
|
@ -8,6 +8,8 @@ def handle(userToken, packetData):
|
||||||
# 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"])
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from common.log import logUtils as log
|
from common.log import logUtils as log
|
||||||
from constants import clientPackets, serverPackets
|
from constants import clientPackets
|
||||||
from constants import exceptions
|
from constants import exceptions
|
||||||
|
from constants import serverPackets
|
||||||
from objects import glob
|
from objects import glob
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,32 +13,26 @@ 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):
|
userToken.joinMatch(matchID)
|
||||||
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.sendUpdates()
|
||||||
match.changePassword(packetData["matchPassword"])
|
match.changePassword(packetData["matchPassword"])
|
||||||
|
|
||||||
|
# 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())
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from common import generalUtils
|
||||||
from common.log import logUtils as log
|
from common.log import logUtils as log
|
||||||
from constants import clientPackets
|
from constants import clientPackets
|
||||||
from constants import exceptions
|
from constants import exceptions
|
||||||
|
@ -17,17 +18,21 @@ def handle(userToken, packetData):
|
||||||
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]
|
||||||
|
|
||||||
# Hash password if needed
|
# Hash password if needed
|
||||||
# if password != "":
|
#if password != "":
|
||||||
# password = generalUtils.stringMd5(password)
|
# 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)
|
userToken.joinMatch(matchID)
|
||||||
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))
|
|
@ -15,7 +15,6 @@ from objects import glob
|
||||||
|
|
||||||
def handle(tornadoRequest):
|
def handle(tornadoRequest):
|
||||||
# Data to return
|
# Data to return
|
||||||
responseToken = None
|
|
||||||
responseTokenString = "ayy"
|
responseTokenString = "ayy"
|
||||||
responseData = bytes()
|
responseData = bytes()
|
||||||
|
|
||||||
|
@ -30,6 +29,9 @@ 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.invalidArgumentsException()
|
||||||
|
@ -44,6 +46,7 @@ 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()
|
||||||
|
@ -61,9 +64,9 @@ def handle(tornadoRequest):
|
||||||
|
|
||||||
# Make sure we are not banned or locked
|
# Make sure we are not banned or locked
|
||||||
priv = userUtils.getPrivileges(userID)
|
priv = userUtils.getPrivileges(userID)
|
||||||
if userUtils.isBanned(userID) and priv & privileges.USER_PENDING_VERIFICATION == 0:
|
if userUtils.isBanned(priv=priv) == True and not userUtils.isPending(priv=priv):
|
||||||
raise exceptions.loginBannedException()
|
raise exceptions.loginBannedException()
|
||||||
if userUtils.isLocked(userID) and priv & privileges.USER_PENDING_VERIFICATION == 0:
|
if userUtils.isLocked(priv=priv) == True and not userUtils.isPending(priv=priv):
|
||||||
raise exceptions.loginLockedException()
|
raise exceptions.loginLockedException()
|
||||||
|
|
||||||
# 2FA check
|
# 2FA check
|
||||||
|
@ -75,7 +78,7 @@ 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 userUtils.hasVerifiedHardware(userID) == False:
|
||||||
if userUtils.verifyUser(userID, clientData):
|
if userUtils.verifyUser(userID, clientData):
|
||||||
# Valid account
|
# Valid account
|
||||||
log.info("Account {} verified successfully!".format(userID))
|
log.info("Account {} verified successfully!".format(userID))
|
||||||
|
@ -118,9 +121,6 @@ def handle(tornadoRequest):
|
||||||
expireIn = "{} days".format(expireDays) if expireDays > 1 else "less than 24 hours"
|
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)))
|
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 = userUtils.getSilenceEnd(userID)
|
||||||
|
@ -176,7 +176,7 @@ def handle(tornadoRequest):
|
||||||
|
|
||||||
# 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,33 +186,29 @@ 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:
|
||||||
# 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)
|
||||||
|
|
||||||
|
# Set country in db if user has no country (first bancho login)
|
||||||
|
if userUtils.getCountry(userID) == "XX":
|
||||||
|
userUtils.setCountry(userID, 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(userUtils.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)
|
|
||||||
if userUtils.getCountry(userID) == "XX":
|
|
||||||
userUtils.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 or tournament
|
||||||
if not responseToken.restricted:
|
if not responseToken.restricted:
|
||||||
|
@ -224,23 +220,25 @@ 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.invalidArgumentsException:
|
||||||
# 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:
|
except exceptions.loginLockedException:
|
||||||
# Login banned error packet
|
# Login banned error packet
|
||||||
|
err = True
|
||||||
responseData += serverPackets.loginLocked()
|
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,6 +251,7 @@ 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 prehistory! Please turn update it from the settings!")
|
||||||
except:
|
except:
|
||||||
|
@ -260,7 +259,10 @@ def handle(tornadoRequest):
|
||||||
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
|
|
@ -1,5 +1,4 @@
|
||||||
import time
|
import time
|
||||||
import json
|
|
||||||
|
|
||||||
from common.log import logUtils as log
|
from common.log import logUtils as log
|
||||||
from constants import serverPackets
|
from constants import serverPackets
|
||||||
|
@ -7,7 +6,7 @@ from helpers import chatHelper as chat
|
||||||
from objects import glob
|
from objects import glob
|
||||||
|
|
||||||
|
|
||||||
def handle(userToken, _=None, deleteToken=True):
|
def handle(userToken, _=None):
|
||||||
# get usertoken data
|
# get usertoken data
|
||||||
userID = userToken.userID
|
userID = userToken.userID
|
||||||
username = userToken.username
|
username = userToken.username
|
||||||
|
@ -17,7 +16,7 @@ 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):
|
||||||
# Stop spectating
|
# Stop spectating
|
||||||
userToken.stopSpectating()
|
userToken.stopSpectating()
|
||||||
|
|
||||||
|
@ -35,23 +34,11 @@ def handle(userToken, _=None, deleteToken=True):
|
||||||
glob.streams.broadcast("main", serverPackets.userLogout(userID))
|
glob.streams.broadcast("main", 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. (logout)".format(username))
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from objects import glob
|
from objects import glob
|
||||||
from constants import serverPackets, clientPackets
|
from constants import slotStatuses
|
||||||
|
from constants import serverPackets
|
||||||
|
|
||||||
def handle(userToken, packetData):
|
def handle(userToken, packetData):
|
||||||
# Get usertoken data
|
# Get usertoken data
|
||||||
|
@ -16,16 +17,11 @@ 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"])
|
glob.streams.broadcast(match.playingStreamName, serverPackets.matchFrames(slotID, packetData))
|
||||||
match.updateHP(slotID, data["currentHp"])
|
|
||||||
|
|
||||||
# Enqueue frames to who's playing
|
|
||||||
glob.streams.broadcast(match.playingStreamName, serverPackets.matchFrames(slotID, packetData))
|
|
|
@ -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"])
|
|
||||||
|
|
|
@ -12,16 +12,16 @@ 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:
|
# Host check
|
||||||
# Host check
|
if userID != match.hostUserID:
|
||||||
if userID != match.hostUserID:
|
return
|
||||||
return
|
|
||||||
|
|
||||||
# Make sure we aren't locking our slot
|
# Make sure we aren't locking our slot
|
||||||
ourSlot = match.getUserSlotID(userID)
|
ourSlot = match.getUserSlotID(userID)
|
||||||
if packetData["slotID"] == ourSlot:
|
if packetData["slotID"] == ourSlot:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Lock/Unlock slot
|
# Lock/Unlock slot
|
||||||
match.toggleSlotLocked(packetData["slotID"])
|
match.toggleSlotLock(packetData["slotID"])
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
|
@ -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 is not 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()
|
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
from objects import glob
|
from objects import glob
|
||||||
|
from constants import slotStatuses
|
||||||
|
from constants import serverPackets
|
||||||
|
|
||||||
def handle(userToken, _):
|
def handle(userToken, _):
|
||||||
|
|
||||||
|
@ -13,9 +15,11 @@ 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()
|
# Host check
|
||||||
|
if userToken.userID != match.hostUserID:
|
||||||
|
return
|
||||||
|
|
||||||
|
match.start()
|
||||||
|
|
|
@ -16,10 +16,12 @@ 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
|
# Host check
|
||||||
match.transferHost(packetData["slotID"])
|
if userToken.userID != match.hostUserID:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Transfer host
|
||||||
|
match.transferHost(packetData["slotID"])
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
from common.log import logUtils as log
|
from common.log import logUtils as log
|
||||||
from helpers import chatHelper as chat
|
from helpers import chatHelper as chat
|
||||||
|
from objects import glob
|
||||||
|
|
||||||
|
|
||||||
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")
|
userToken.leaveStream("lobby")
|
||||||
|
|
||||||
# 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
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
from common.log import logUtils as log
|
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
|
|
||||||
|
|
||||||
|
|
||||||
def handle(userToken, packetData):
|
def handle(userToken, packetData):
|
||||||
|
@ -12,12 +11,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"]))
|
||||||
|
|
|
@ -1,15 +1,31 @@
|
||||||
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)
|
glob.streams.broadcast("spect/{}".format(userID), serverPackets.spectatorFrames(packetData[7:]))
|
||||||
glob.streams.broadcast(streamName, serverPackets.spectatorFrames(packetData[7:]))
|
'''for i in userToken.spectators:
|
||||||
log.debug("Broadcasting {}'s frames to {} clients".format(
|
# Send to every user but host
|
||||||
userID,
|
if i != userID:
|
||||||
len(glob.streams.streams[streamName].clients))
|
try:
|
||||||
)
|
# Get spectator token object
|
||||||
|
spectatorToken = glob.tokens.getTokenFromUserID(i)
|
||||||
|
|
||||||
|
# Make sure the token exists
|
||||||
|
if spectatorToken is 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))'''
|
||||||
|
|
|
@ -8,11 +8,6 @@ def handle(userToken, packetData):
|
||||||
# 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
|
|
||||||
if packetData["userID"] < 0:
|
|
||||||
userToken.stopSpectating()
|
|
||||||
return
|
|
||||||
|
|
||||||
# Get host token
|
# Get host token
|
||||||
targetToken = glob.tokens.getTokenFromUserID(packetData["userID"])
|
targetToken = glob.tokens.getTokenFromUserID(packetData["userID"])
|
||||||
if targetToken is None:
|
if targetToken is None:
|
||||||
|
|
|
@ -5,7 +5,7 @@ from helpers import chatHelper as chat
|
||||||
def handle(userToken, packetData):
|
def handle(userToken, packetData):
|
||||||
packetData = clientPackets.tournamentJoinMatchChannel(packetData)
|
packetData = clientPackets.tournamentJoinMatchChannel(packetData)
|
||||||
matchID = packetData["matchID"]
|
matchID = packetData["matchID"]
|
||||||
if matchID not in glob.matches.matches or not userToken.tournament:
|
if matchID not in glob.matches.matches:
|
||||||
return
|
return
|
||||||
userToken.matchID = matchID
|
userToken.matchID = matchID
|
||||||
chat.joinChannel(token=userToken, channel="#multi_{}".format(matchID), force=True)
|
chat.joinChannel(token=userToken, channel="#multi_{}".format(matchID))
|
|
@ -5,7 +5,7 @@ from helpers import chatHelper as chat
|
||||||
def handle(userToken, packetData):
|
def handle(userToken, packetData):
|
||||||
packetData = clientPackets.tournamentLeaveMatchChannel(packetData)
|
packetData = clientPackets.tournamentLeaveMatchChannel(packetData)
|
||||||
matchID = packetData["matchID"]
|
matchID = packetData["matchID"]
|
||||||
if matchID not in glob.matches.matches or not userToken.tournament:
|
if matchID not in glob.matches.matches:
|
||||||
return
|
return
|
||||||
chat.partChannel(token=userToken, channel="#multi_{}".format(matchID), force=True)
|
chat.partChannel(token=userToken, channel="#multi_{}".format(matchID))
|
||||||
userToken.matchID = 0
|
userToken.matchID = 0
|
|
@ -1,10 +1,10 @@
|
||||||
from constants import clientPackets
|
from constants import clientPackets
|
||||||
|
from constants import serverPackets
|
||||||
from objects import glob
|
from objects import glob
|
||||||
|
|
||||||
def handle(userToken, packetData):
|
def handle(userToken, packetData):
|
||||||
packetData = clientPackets.tournamentMatchInfoRequest(packetData)
|
packetData = clientPackets.tournamentMatchInfoRequest(packetData)
|
||||||
matchID = packetData["matchID"]
|
matchID = packetData["matchID"]
|
||||||
if matchID not in glob.matches.matches or not userToken.tournament:
|
if matchID not in glob.matches.matches:
|
||||||
return
|
return
|
||||||
with glob.matches.matches[matchID] as m:
|
userToken.enqueue(glob.matches.matches[matchID].matchDataCache)
|
||||||
userToken.enqueue(m.matchDataCache)
|
|
|
@ -14,10 +14,3 @@ 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
|
|
|
@ -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
|
|
|
@ -1,9 +1,6 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import tornado.web
|
from common.log import logUtils as log
|
||||||
import tornado.gen
|
|
||||||
|
|
||||||
from common.sentry import sentry
|
|
||||||
from common.web import requestsManager
|
from common.web import requestsManager
|
||||||
from constants import exceptions
|
from constants import exceptions
|
||||||
from helpers import chatHelper
|
from helpers import chatHelper
|
||||||
|
@ -11,9 +8,6 @@ from objects import glob
|
||||||
|
|
||||||
|
|
||||||
class handler(requestsManager.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"}
|
||||||
|
@ -27,11 +21,8 @@ class handler(requestsManager.asyncRequestHandler):
|
||||||
if key is None or key != glob.conf.config["server"]["cikey"]:
|
if key is None or key != glob.conf.config["server"]["cikey"]:
|
||||||
raise exceptions.invalidArgumentsException()
|
raise exceptions.invalidArgumentsException()
|
||||||
|
|
||||||
chatHelper.sendMessage(
|
log.info("API REQUEST FOR FOKABOT MESSAGE AAAAAAA")
|
||||||
glob.BOT_NAME,
|
chatHelper.sendMessage("FokaBot", self.get_argument("to"), self.get_argument("msg"))
|
||||||
self.get_argument("to").encode().decode("ASCII", "ignore"),
|
|
||||||
self.get_argument("msg").encode().decode("ASCII", "ignore")
|
|
||||||
)
|
|
||||||
|
|
||||||
# Status code and message
|
# Status code and message
|
||||||
statusCode = 200
|
statusCode = 200
|
||||||
|
@ -44,5 +35,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))
|
||||||
|
|
|
@ -1,19 +1,11 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import tornado.web
|
|
||||||
import tornado.gen
|
|
||||||
|
|
||||||
from common.sentry import sentry
|
|
||||||
from common.ripple import userUtils
|
|
||||||
from common.web import requestsManager
|
from common.web import requestsManager
|
||||||
from constants import exceptions
|
from constants import exceptions
|
||||||
from objects import glob
|
from objects import glob
|
||||||
|
|
||||||
|
|
||||||
class handler(requestsManager.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"}
|
||||||
|
@ -26,8 +18,7 @@ class handler(requestsManager.asyncRequestHandler):
|
||||||
username = None
|
username = None
|
||||||
userID = None
|
userID = None
|
||||||
if "u" in self.request.arguments:
|
if "u" in self.request.arguments:
|
||||||
#username = self.get_argument("u").lower().replace(" ", "_")
|
username = self.get_argument("u")
|
||||||
username = userUtils.safeUsername(self.get_argument("u"))
|
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
userID = int(self.get_argument("id"))
|
userID = int(self.get_argument("id"))
|
||||||
|
@ -38,7 +29,7 @@ class handler(requestsManager.asyncRequestHandler):
|
||||||
data["result"] = False
|
data["result"] = False
|
||||||
else:
|
else:
|
||||||
if username is not None:
|
if username is not None:
|
||||||
data["result"] = True if glob.tokens.getTokenFromUsername(username, safe=True) is not None else False
|
data["result"] = True if glob.tokens.getTokenFromUsername(username) is not None else False
|
||||||
else:
|
else:
|
||||||
data["result"] = True if glob.tokens.getTokenFromUserID(userID) is not None else False
|
data["result"] = True if glob.tokens.getTokenFromUserID(userID) is not None else False
|
||||||
|
|
||||||
|
@ -53,5 +44,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))
|
||||||
|
|
|
@ -1,23 +1,16 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import tornado.web
|
|
||||||
import tornado.gen
|
|
||||||
|
|
||||||
from common.sentry import sentry
|
|
||||||
from common.web import requestsManager
|
from common.web import requestsManager
|
||||||
from objects import glob
|
from objects import glob
|
||||||
|
|
||||||
|
|
||||||
class handler(requestsManager.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 +20,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))
|
||||||
|
|
|
@ -1,23 +1,16 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import tornado.web
|
|
||||||
import tornado.gen
|
|
||||||
|
|
||||||
from common.sentry import sentry
|
|
||||||
from common.web import requestsManager
|
from common.web import requestsManager
|
||||||
from objects import glob
|
from objects import glob
|
||||||
|
|
||||||
|
|
||||||
class handler(requestsManager.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 +20,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))
|
||||||
|
|
|
@ -1,18 +1,11 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import tornado.web
|
|
||||||
import tornado.gen
|
|
||||||
|
|
||||||
from common.sentry import sentry
|
|
||||||
from common.web import requestsManager
|
from common.web import requestsManager
|
||||||
from constants import exceptions
|
from constants import exceptions
|
||||||
from objects import glob
|
from objects import glob
|
||||||
|
|
||||||
|
|
||||||
class handler(requestsManager.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"}
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import tornado.web
|
|
||||||
import tornado.gen
|
|
||||||
|
|
||||||
from common.sentry import sentry
|
|
||||||
from common.log import logUtils as log
|
from common.log import logUtils as log
|
||||||
from common.web import requestsManager
|
from common.web import requestsManager
|
||||||
from constants import exceptions
|
from constants import exceptions
|
||||||
|
@ -12,9 +8,6 @@ from objects import glob
|
||||||
|
|
||||||
|
|
||||||
class handler(requestsManager.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"}
|
||||||
|
|
270
handlers/mainHandler.py
Normal file
270
handlers/mainHandler.py
Normal file
|
@ -0,0 +1,270 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
class handler(SentryMixin, requestsManager.asyncRequestHandler):
|
||||||
|
@tornado.web.asynchronous
|
||||||
|
@tornado.gen.engine
|
||||||
|
def asyncPost(self):
|
||||||
|
try:
|
||||||
|
# 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.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),
|
||||||
|
|
||||||
|
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 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()
|
||||||
|
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 token lock
|
||||||
|
userToken.lock.release()
|
||||||
|
|
||||||
|
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")
|
||||||
|
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>© Ripple team, 2016</i></pre></body></html>"
|
||||||
|
self.write(html)
|
|
@ -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>© Ripple team, 2016</i></pre></body></html>"
|
|
||||||
self.write(html)
|
|
|
@ -8,16 +8,18 @@ from objects import fokabot
|
||||||
from objects import glob
|
from objects import glob
|
||||||
|
|
||||||
|
|
||||||
def joinChannel(userID = 0, channel = "", token = None, toIRC = True, force=False):
|
def joinChannel(userID = 0, channel = "", token = None, toIRC = True):
|
||||||
"""
|
"""
|
||||||
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
|
||||||
|
@ -28,67 +30,76 @@ def joinChannel(userID = 0, channel = "", token = None, toIRC = True, force=Fals
|
||||||
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 is 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 is 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)
|
||||||
|
@ -108,24 +119,12 @@ 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
|
||||||
|
@ -133,20 +132,17 @@ def partChannel(userID = 0, channel = "", token = None, toIRC = True, kick = Fal
|
||||||
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
|
||||||
|
@ -155,28 +151,35 @@ 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 is None:
|
||||||
token = glob.tokens.getTokenFromUsername(fro)
|
token = glob.tokens.getTokenFromUsername(fro)
|
||||||
if token is None:
|
if token is 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
|
||||||
|
|
||||||
|
# Set some variables
|
||||||
|
userID = token.userID
|
||||||
|
username = token.username
|
||||||
|
|
||||||
# Make sure this is not a tournament client
|
# Make sure this is not a tournament client
|
||||||
# if token.tournament:
|
if token.tournament:
|
||||||
# raise exceptions.userTournamentException()
|
raise exceptions.userTournamentException()
|
||||||
|
|
||||||
# Make sure the user is not in restricted mode
|
# Make sure the user is not in restricted mode
|
||||||
if token.restricted:
|
if token.restricted:
|
||||||
|
@ -186,16 +189,12 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
|
||||||
if token.isSilenced():
|
if token.isSilenced():
|
||||||
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 is None:
|
||||||
s = token.userID
|
s = userID
|
||||||
else:
|
else:
|
||||||
s = token.spectatingUserID
|
s = token.spectatingUserID
|
||||||
to = "#spect_{}".format(s)
|
to = "#spect_{}".format(s)
|
||||||
|
@ -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,7 +212,7 @@ 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("#")
|
||||||
|
@ -225,113 +220,98 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = 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 is None:
|
||||||
raise exceptions.userNotFoundException()
|
raise exceptions.userNotFoundException
|
||||||
|
|
||||||
# Make sure the recipient is not a tournament client
|
# Make sure the recipient is not a tournament client
|
||||||
#if recipientToken.tournament:
|
if recipientToken.tournament:
|
||||||
# raise exceptions.userTournamentException()
|
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
|
# Away check
|
||||||
if recipientToken.awayCheck(token.userID):
|
if recipientToken.awayCheck(userID):
|
||||||
sendMessage(to, fro, "\x01ACTION is away: {}\x01".format(recipientToken.awayMessage))
|
sendMessage(to, fro, "\x01ACTION is away: {message}\x01".format(code=chr(int(1)), message=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:
|
||||||
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("#") and not (message.startswith("\x01ACTION is playing") and to.startswith("#spect_")):
|
||||||
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")))
|
glob.schiavo.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
|
return 404
|
||||||
except exceptions.userTournamentException:
|
except exceptions.userTournamentException:
|
||||||
log.warning("{} tried to send a message {}, but the recipient is a tournament client".format(token.username, to))
|
log.warning("{} tried to send a message {}, but the recipient is a tournament client".format(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):
|
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
|
# If there are no spaces or underscores in the name
|
||||||
# return it
|
# return it
|
||||||
if " " not in username and "_" not in username:
|
if " " not in username and "_" not in username:
|
||||||
|
@ -346,22 +326,9 @@ def fixUsernameForBancho(username):
|
||||||
return username.replace("_", " ")
|
return username.replace("_", " ")
|
||||||
|
|
||||||
def fixUsernameForIRC(username):
|
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(" ", "_")
|
return username.replace(" ", "_")
|
||||||
|
|
||||||
def IRCConnect(username):
|
def IRCConnect(username):
|
||||||
"""
|
|
||||||
Handle IRC login bancho-side.
|
|
||||||
Add token and broadcast login packet.
|
|
||||||
|
|
||||||
:param username: username
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
userID = userUtils.getID(username)
|
userID = userUtils.getID(username)
|
||||||
if not userID:
|
if not userID:
|
||||||
log.warning("{} doesn't exist".format(username))
|
log.warning("{} doesn't exist".format(username))
|
||||||
|
@ -372,13 +339,6 @@ def IRCConnect(username):
|
||||||
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 is None:
|
||||||
log.warning("{} doesn't exist".format(username))
|
log.warning("{} doesn't exist".format(username))
|
||||||
|
@ -387,13 +347,6 @@ def IRCDisconnect(username):
|
||||||
log.info("{} disconnected from IRC".format(username))
|
log.info("{} disconnected from IRC".format(username))
|
||||||
|
|
||||||
def IRCJoinChannel(username, channel):
|
def IRCJoinChannel(username, channel):
|
||||||
"""
|
|
||||||
Handle IRC channel join bancho-side.
|
|
||||||
|
|
||||||
:param username: username
|
|
||||||
:param channel: channel name
|
|
||||||
:return: IRC return code
|
|
||||||
"""
|
|
||||||
userID = userUtils.getID(username)
|
userID = userUtils.getID(username)
|
||||||
if not userID:
|
if not userID:
|
||||||
log.warning("{} doesn't exist".format(username))
|
log.warning("{} doesn't exist".format(username))
|
||||||
|
@ -404,13 +357,6 @@ def IRCJoinChannel(username, channel):
|
||||||
return joinChannel(userID, channel)
|
return joinChannel(userID, channel)
|
||||||
|
|
||||||
def IRCPartChannel(username, channel):
|
def IRCPartChannel(username, channel):
|
||||||
"""
|
|
||||||
Handle IRC channel part bancho-side.
|
|
||||||
|
|
||||||
:param username: username
|
|
||||||
:param channel: channel name
|
|
||||||
:return: IRC return code
|
|
||||||
"""
|
|
||||||
userID = userUtils.getID(username)
|
userID = userUtils.getID(username)
|
||||||
if not userID:
|
if not userID:
|
||||||
log.warning("{} doesn't exist".format(username))
|
log.warning("{} doesn't exist".format(username))
|
||||||
|
@ -418,16 +364,9 @@ def IRCPartChannel(username, channel):
|
||||||
return partChannel(userID, channel)
|
return partChannel(userID, channel)
|
||||||
|
|
||||||
def IRCAway(username, message):
|
def IRCAway(username, message):
|
||||||
"""
|
|
||||||
Handle IRC away command bancho-side.
|
|
||||||
|
|
||||||
:param username:
|
|
||||||
:param message: away message
|
|
||||||
:return: IRC return code
|
|
||||||
"""
|
|
||||||
userID = userUtils.getID(username)
|
userID = userUtils.getID(username)
|
||||||
if not userID:
|
if not userID:
|
||||||
log.warning("{} doesn't exist".format(username))
|
log.warning("{} doesn't exist".format(username))
|
||||||
return
|
return
|
||||||
glob.tokens.getTokenFromUserID(userID).awayMessage = message
|
glob.tokens.getTokenFromUserID(userID).setAwayMessage(message)
|
||||||
return 305 if message == "" else 306
|
return 305 if message == "" else 306
|
|
@ -5,9 +5,9 @@ class config:
|
||||||
# Check if config.ini exists and load/generate it
|
# Check if config.ini exists and load/generate it
|
||||||
def __init__(self, file):
|
def __init__(self, file):
|
||||||
"""
|
"""
|
||||||
Initialize a config file object
|
Initialize a config object
|
||||||
|
|
||||||
:param file: file name
|
file -- filename
|
||||||
"""
|
"""
|
||||||
self.config = configparser.ConfigParser()
|
self.config = configparser.ConfigParser()
|
||||||
self.default = True
|
self.default = True
|
||||||
|
@ -25,9 +25,9 @@ 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
|
||||||
|
@ -37,27 +37,22 @@ 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","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("cheesegull", "apiurl")
|
self.config.get("mirror","url")
|
||||||
self.config.get("cheesegull", "apikey")
|
self.config.get("mirror","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")
|
||||||
|
@ -73,17 +68,13 @@ class config:
|
||||||
|
|
||||||
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):
|
def generateDefaultConfig(self):
|
||||||
"""
|
"""
|
||||||
Write a default config file to disk
|
Open and set default keys for that config file
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
# Open config.ini in write mode
|
# Open config.ini in write mode
|
||||||
f = open(self.fileName, "w")
|
f = open(self.fileName, "w")
|
||||||
|
@ -96,12 +87,6 @@ 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", "threads", "16")
|
||||||
|
@ -109,9 +94,9 @@ class config:
|
||||||
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.add_section("cheesegull")
|
self.config.add_section("mirror")
|
||||||
self.config.set("cheesegull", "apiurl", "http://cheesegu.ll/api")
|
self.config.set("mirror", "url", "http://storage.ripple.moe")
|
||||||
self.config.set("cheesegull", "apikey", "")
|
self.config.set("mirror", "apikey", "anotherkey")
|
||||||
|
|
||||||
self.config.add_section("debug")
|
self.config.add_section("debug")
|
||||||
self.config.set("debug", "enable", "0")
|
self.config.set("debug", "enable", "0")
|
||||||
|
@ -120,8 +105,8 @@ 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")
|
||||||
|
@ -129,9 +114,9 @@ class config:
|
||||||
self.config.set("discord", "devgroup", "")
|
self.config.set("discord", "devgroup", "")
|
||||||
|
|
||||||
self.config.add_section("datadog")
|
self.config.add_section("datadog")
|
||||||
self.config.set("datadog", "enable", "0")
|
self.config.set("datadog", "enable")
|
||||||
self.config.set("datadog", "apikey", "")
|
self.config.set("datadog", "apikey")
|
||||||
self.config.set("datadog", "appkey", "")
|
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")
|
||||||
|
@ -142,9 +127,6 @@ class config:
|
||||||
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()
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
from common.constants import bcolors
|
from common.constants import bcolors
|
||||||
from objects import glob
|
from objects import glob
|
||||||
|
|
||||||
def printServerStartHeader(asciiArt=True):
|
def printServerStartHeader(asciiArt):
|
||||||
"""
|
"""
|
||||||
Print server start message
|
Print server start header with optional ascii art
|
||||||
|
|
||||||
:param asciiArt: print BanchoBoat ascii art. Default: True
|
asciiArt -- if True, will print ascii art too
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
if asciiArt:
|
if asciiArt:
|
||||||
print("{} _ __".format(bcolors.GREEN))
|
print("{} _ __".format(bcolors.GREEN))
|
||||||
|
@ -27,52 +26,41 @@ 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://git.zxq.co/ripple/pep.py".format(bcolors.UNDERLINE), bcolors.GREEN)
|
||||||
printColored("> Custom branch by the osufx team (just Sunpy)", bcolors.GREEN)
|
|
||||||
printColored("> {}https://github.com/osufx/pep.py".format(bcolors.UNDERLINE), bcolors.GREEN)
|
|
||||||
printColored("> Press CTRL+C to exit\n", 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():
|
def printError():
|
||||||
"""
|
"""
|
||||||
Print a red "Error"
|
Print error text FOR LOADING
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
printColored("Error", bcolors.RED)
|
printColored("Error", bcolors.RED)
|
||||||
|
|
||||||
def printDone():
|
def printDone():
|
||||||
"""
|
"""
|
||||||
Print a green "Done"
|
Print error text FOR LOADING
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
printColored("Done", bcolors.GREEN)
|
printColored("Done", bcolors.GREEN)
|
||||||
|
|
||||||
def printWarning():
|
def printWarning():
|
||||||
"""
|
"""
|
||||||
Print a yellow "Warning"
|
Print error text FOR LOADING
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
printColored("Warning", bcolors.YELLOW)
|
printColored("Warning", bcolors.YELLOW)
|
||||||
|
|
|
@ -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,
|
||||||
|
@ -254,11 +255,12 @@ countryCodes = {
|
||||||
|
|
||||||
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 +270,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
|
||||||
|
|
|
@ -7,10 +7,10 @@ from objects import glob
|
||||||
|
|
||||||
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
|
||||||
|
@ -22,15 +22,15 @@ def getCountry(ip):
|
||||||
|
|
||||||
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]
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import struct
|
import struct
|
||||||
from constants import dataTypes
|
from constants import dataTypes
|
||||||
|
|
||||||
cpdef bytearray uleb128Encode(int num):
|
def uleb128Encode(num):
|
||||||
"""
|
"""
|
||||||
Encode an int to uleb128
|
Encode int -> uleb128
|
||||||
|
|
||||||
:param num: int to encode
|
num -- int to encode
|
||||||
:return: bytearray with encoded number
|
return -- bytearray with encoded number
|
||||||
"""
|
"""
|
||||||
cdef bytearray arr = bytearray()
|
arr = bytearray()
|
||||||
cdef int length = 0
|
length = 0
|
||||||
|
|
||||||
if num == 0:
|
if num == 0:
|
||||||
return bytearray(b"\x00")
|
return bytearray(b"\x00")
|
||||||
|
@ -23,16 +23,15 @@ cpdef bytearray uleb128Encode(int num):
|
||||||
|
|
||||||
return arr
|
return arr
|
||||||
|
|
||||||
cpdef list uleb128Decode(bytes num):
|
def uleb128Decode(num):
|
||||||
"""
|
"""
|
||||||
Decode a uleb128 to int
|
Decode uleb128 -> int
|
||||||
|
|
||||||
:param num: encoded uleb128 int
|
num -- encoded uleb128
|
||||||
:return: (total, length)
|
return -- list. [total, length]
|
||||||
"""
|
"""
|
||||||
cdef int shift = 0
|
shift = 0
|
||||||
cdef list arr = [0,0] #total, length
|
arr = [0,0] #total, length
|
||||||
cdef int b
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
b = num[arr[1]]
|
b = num[arr[1]]
|
||||||
|
@ -44,14 +43,16 @@ cpdef list uleb128Decode(bytes num):
|
||||||
|
|
||||||
return arr
|
return arr
|
||||||
|
|
||||||
cpdef unpackData(bytes data, int dataType):
|
def unpackData(data, dataType):
|
||||||
"""
|
"""
|
||||||
Unpacks a single section of a packet.
|
Unpacks data according to dataType
|
||||||
|
|
||||||
:param data: bytes to unpack
|
data -- bytes array to unpack
|
||||||
:param dataType: data type
|
dataType -- data type. See dataTypes.py
|
||||||
:return: unpacked bytes
|
|
||||||
|
return -- unpacked bytes
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Get right pack Type
|
# Get right pack Type
|
||||||
if dataType == dataTypes.UINT16:
|
if dataType == dataTypes.UINT16:
|
||||||
unpackType = "<H"
|
unpackType = "<H"
|
||||||
|
@ -75,17 +76,18 @@ cpdef unpackData(bytes data, int dataType):
|
||||||
# Unpack
|
# Unpack
|
||||||
return struct.unpack(unpackType, bytes(data))[0]
|
return struct.unpack(unpackType, bytes(data))[0]
|
||||||
|
|
||||||
cpdef bytes packData(__data, int dataType):
|
def packData(__data, dataType):
|
||||||
"""
|
"""
|
||||||
Packs a single section of a packet.
|
Packs data according to dataType
|
||||||
|
|
||||||
:param __data: data to pack
|
data -- bytes to pack
|
||||||
:param dataType: data type
|
dataType -- data type. See dataTypes.py
|
||||||
:return: packed bytes
|
|
||||||
|
return -- packed bytes
|
||||||
"""
|
"""
|
||||||
cdef bytes data = bytes() # data to return
|
|
||||||
cdef bint pack = True # if True, use pack. False only with strings
|
data = bytes() # data to return
|
||||||
cdef str packType
|
pack = True # if True, use pack. False only with strings
|
||||||
|
|
||||||
# Get right pack Type
|
# Get right pack Type
|
||||||
if dataType == dataTypes.BBYTES:
|
if dataType == dataTypes.BBYTES:
|
||||||
|
@ -136,24 +138,23 @@ cpdef bytes packData(__data, int dataType):
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
cpdef bytes buildPacket(int __packet, list __packetData = None):
|
def buildPacket(__packet, __packetData=None):
|
||||||
"""
|
"""
|
||||||
Builds a packet
|
Build a packet
|
||||||
|
|
||||||
:param __packet: packet ID
|
packet -- packet id (int)
|
||||||
:param __packetData: packet structure [[data, dataType], [data, dataType], ...]
|
packetData -- list [[data, dataType], [data, dataType], ...]
|
||||||
:return: packet bytes
|
|
||||||
|
return -- packet bytes
|
||||||
"""
|
"""
|
||||||
# Default argument
|
# Set some variables
|
||||||
if __packetData is None:
|
if __packetData is None:
|
||||||
__packetData = []
|
__packetData = []
|
||||||
# Set some variables
|
packetData = bytes()
|
||||||
cdef bytes packetData = bytes()
|
packetLength = 0
|
||||||
cdef int packetLength = 0
|
packetBytes = bytes()
|
||||||
cdef bytes packetBytes = bytes()
|
|
||||||
|
|
||||||
# Pack packet data
|
# Pack packet data
|
||||||
cdef list i
|
|
||||||
for i in __packetData:
|
for i in __packetData:
|
||||||
packetData += packData(i[0], i[1])
|
packetData += packData(i[0], i[1])
|
||||||
|
|
||||||
|
@ -167,43 +168,42 @@ cpdef bytes buildPacket(int __packet, list __packetData = None):
|
||||||
packetBytes += packetData # packet data
|
packetBytes += packetData # packet data
|
||||||
return packetBytes
|
return packetBytes
|
||||||
|
|
||||||
cpdef int readPacketID(bytes stream):
|
def readPacketID(stream):
|
||||||
"""
|
"""
|
||||||
Read packetID (first two bytes) from a packet
|
Read packetID from stream (0-1 bytes)
|
||||||
|
|
||||||
:param stream: packet bytes
|
stream -- data stream
|
||||||
:return: packet ID
|
return -- packet ID (int)
|
||||||
"""
|
"""
|
||||||
return unpackData(stream[0:2], dataTypes.UINT16)
|
return unpackData(stream[0:2], dataTypes.UINT16)
|
||||||
|
|
||||||
cpdef int readPacketLength(bytes stream):
|
def readPacketLength(stream):
|
||||||
"""
|
"""
|
||||||
Read packet data length (3:7 bytes) from a packet
|
Read packet length from stream (3-4-5-6 bytes)
|
||||||
|
|
||||||
:param stream: packet bytes
|
stream -- data stream
|
||||||
:return: packet data length
|
return -- packet length (int)
|
||||||
"""
|
"""
|
||||||
return unpackData(stream[3:7], dataTypes.UINT32)
|
return unpackData(stream[3:7], dataTypes.UINT32)
|
||||||
|
|
||||||
|
|
||||||
cpdef readPacketData(bytes stream, list structure=None, bint hasFirstBytes = True):
|
def readPacketData(stream, structure=None, hasFirstBytes = True):
|
||||||
"""
|
"""
|
||||||
Read packet data from `stream` according to `structure`
|
Read packet data from stream according to structure
|
||||||
:param stream: packet bytes
|
|
||||||
:param structure: packet structure: [[name, dataType], [name, dataType], ...]
|
stream -- data stream
|
||||||
:param hasFirstBytes: if True, `stream` has packetID and length bytes.
|
structure -- [[name, dataType], [name, dataType], ...]
|
||||||
if False, `stream` has only packet data. Default: True
|
hasFirstBytes -- if True, stream has packetID and length bytes.
|
||||||
:return: {name: unpackedValue, ...}
|
if False, stream has only packetData.
|
||||||
|
Optional. Default: True
|
||||||
|
return -- dictionary. key: name, value: read data
|
||||||
"""
|
"""
|
||||||
# Default list argument
|
# Read packet ID (first 2 bytes)
|
||||||
if structure is None:
|
if structure is None:
|
||||||
structure = []
|
structure = []
|
||||||
|
data = {}
|
||||||
# Read packet ID (first 2 bytes)
|
|
||||||
cdef dict data = {}
|
|
||||||
|
|
||||||
# Skip packet ID and packet length if needed
|
# Skip packet ID and packet length if needed
|
||||||
cdef start, end
|
|
||||||
if hasFirstBytes:
|
if hasFirstBytes:
|
||||||
end = 7
|
end = 7
|
||||||
start = 7
|
start = 7
|
||||||
|
@ -212,8 +212,6 @@ cpdef readPacketData(bytes stream, list structure=None, bint hasFirstBytes = Tru
|
||||||
start = 0
|
start = 0
|
||||||
|
|
||||||
# Read packet
|
# Read packet
|
||||||
cdef list i
|
|
||||||
cdef bint unpack
|
|
||||||
for i in structure:
|
for i in structure:
|
||||||
start = end
|
start = end
|
||||||
unpack = True
|
unpack = True
|
||||||
|
@ -248,10 +246,7 @@ cpdef readPacketData(bytes stream, list structure=None, bint hasFirstBytes = Tru
|
||||||
end = start+length[0]+length[1]+1
|
end = start+length[0]+length[1]+1
|
||||||
|
|
||||||
# Read bytes
|
# Read bytes
|
||||||
#data[i[0]] = ''.join(chr(j) for j in stream[start+1+length[1]:end])
|
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:
|
elif i[1] == dataTypes.BYTE:
|
||||||
end = start+1
|
end = start+1
|
||||||
elif i[1] == dataTypes.UINT16 or i[1] == dataTypes.SINT16:
|
elif i[1] == dataTypes.UINT16 or i[1] == dataTypes.SINT16:
|
|
@ -17,7 +17,6 @@ from objects import glob
|
||||||
def dispose():
|
def dispose():
|
||||||
"""
|
"""
|
||||||
Perform some clean up. Called on shutdown.
|
Perform some clean up. Called on shutdown.
|
||||||
|
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
print("> Disposing server... ")
|
print("> Disposing server... ")
|
||||||
|
@ -28,7 +27,7 @@ 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
|
||||||
|
|
||||||
|
@ -36,14 +35,12 @@ def scheduleShutdown(sendRestartTime, restart, message = "", delay=20):
|
||||||
"""
|
"""
|
||||||
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+delay))
|
||||||
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
|
||||||
|
@ -64,21 +61,13 @@ def scheduleShutdown(sendRestartTime, restart, message = "", delay=20):
|
||||||
threading.Timer(sendRestartTime+delay, action).start()
|
threading.Timer(sendRestartTime+delay, 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()
|
dispose()
|
||||||
os.execv(sys.executable, [sys.executable] + sys.argv)
|
os.execv(sys.executable, [sys.executable] + sys.argv)
|
||||||
|
|
||||||
def shutdownServer():
|
def shutdownServer():
|
||||||
"""
|
"""Shutdown pep.py"""
|
||||||
Shutdown pep.py
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
log.info("Shutting down pep.py...")
|
log.info("Shutting down pep.py...")
|
||||||
dispose()
|
dispose()
|
||||||
sig = signal.SIGKILL if runningUnderUnix() else signal.CTRL_C_EVENT
|
sig = signal.SIGKILL if runningUnderUnix() else signal.CTRL_C_EVENT
|
||||||
|
@ -88,7 +77,7 @@ 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 = {"unix": runningUnderUnix(), "connectedUsers": len(glob.tokens.tokens), "matches": len(glob.matches.matches)}
|
||||||
|
|
||||||
|
|
167
irc/ircserver.py
167
irc/ircserver.py
|
@ -17,21 +17,23 @@ import traceback
|
||||||
import raven
|
import raven
|
||||||
|
|
||||||
from common.log import logUtils as log
|
from common.log import logUtils as log
|
||||||
from common.ripple import userUtils
|
|
||||||
from helpers import chatHelper as chat
|
from helpers import chatHelper as chat
|
||||||
from objects import glob
|
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 = ""
|
||||||
|
@ -45,7 +47,6 @@ class Client:
|
||||||
self.IRCUsername = ""
|
self.IRCUsername = ""
|
||||||
self.banchoUsername = ""
|
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 +60,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 +69,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 +78,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,11 +87,10 @@ 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.IRCUsername
|
||||||
|
@ -105,8 +103,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 +112,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 +121,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 +133,12 @@ class Client:
|
||||||
self.server.removeClient(self, quitmsg)
|
self.server.removeClient(self, quitmsg)
|
||||||
|
|
||||||
# Bancho logout
|
# Bancho logout
|
||||||
if callLogout and self.banchoUsername != "":
|
if callLogout:
|
||||||
chat.IRCDisconnect(self.IRCUsername)
|
chat.IRCDisconnect(self.IRCUsername)
|
||||||
|
|
||||||
|
|
||||||
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)
|
||||||
|
@ -170,11 +161,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,11 +198,7 @@ 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]))
|
||||||
|
@ -223,13 +206,9 @@ class Client:
|
||||||
except socket.error as x:
|
except socket.error as x:
|
||||||
self.disconnect(str(x))
|
self.disconnect(str(x))
|
||||||
|
|
||||||
def checkAlive(self):
|
|
||||||
"""
|
|
||||||
Check if this client is still connected.
|
|
||||||
If the client is dead, disconnect it.
|
|
||||||
|
|
||||||
:return:
|
def checkAlive(self):
|
||||||
"""
|
"""Check if this client is still connected"""
|
||||||
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 +224,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 +253,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 = chat.fixUsernameForIRC(supposedUsername["username"])
|
||||||
self.supposedUserID = supposedUser["id"]
|
|
||||||
self.__handleCommand = self.registerHandler
|
self.__handleCommand = self.registerHandler
|
||||||
else:
|
else:
|
||||||
# Wrong IRC Token
|
# Wrong IRC Token
|
||||||
|
@ -313,11 +283,6 @@ class Client:
|
||||||
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(chat.fixUsernameForBancho(nick), True)
|
||||||
if token is not None:
|
if token is not None:
|
||||||
|
@ -358,11 +323,11 @@ 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.IRCUsername 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")
|
||||||
|
@ -375,13 +340,13 @@ class Client:
|
||||||
|
|
||||||
# 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(",")
|
||||||
|
@ -410,15 +375,13 @@ 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 is None:
|
||||||
continue
|
continue
|
||||||
usernames.append(chat.fixUsernameForIRC(glob.tokens.tokens[user].username))
|
usernames.append(chat.fixUsernameForIRC(token.username))
|
||||||
usernames = " ".join(usernames)
|
usernames = " ".join(usernames)
|
||||||
|
|
||||||
# Send IRC users list
|
# Send IRC users list
|
||||||
|
@ -429,7 +392,7 @@ 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")
|
||||||
|
@ -513,7 +476,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,15 +487,12 @@ class Client:
|
||||||
"""(fake) PONG command handler"""
|
"""(fake) PONG command handler"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def awayHandler(self, _, arguments):
|
def awayHandler(self, command, arguments):
|
||||||
"""AWAY command handler"""
|
|
||||||
response = chat.IRCAway(self.banchoUsername, " ".join(arguments))
|
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")
|
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": self.awayHandler,
|
||||||
#"ISON": ison_handler,
|
#"ISON": ison_handler,
|
||||||
|
@ -562,18 +522,17 @@ class Client:
|
||||||
|
|
||||||
class Server:
|
class Server:
|
||||||
def __init__(self, port):
|
def __init__(self, port):
|
||||||
|
#self.host = socket.getfqdn("127.0.0.1")[:63]
|
||||||
self.host = glob.conf.config["irc"]["hostname"]
|
self.host = glob.conf.config["irc"]["hostname"]
|
||||||
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, isBanchoUsername=True):
|
||||||
"""
|
"""
|
||||||
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.IRCUsername == username and not isBanchoUsername) or (value.banchoUsername == username and isBanchoUsername):
|
||||||
|
@ -584,9 +543,8 @@ class Server:
|
||||||
"""
|
"""
|
||||||
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)
|
username = chat.fixUsernameForIRC(username)
|
||||||
for _, value in self.clients.items():
|
for _, value in self.clients.items():
|
||||||
|
@ -597,9 +555,8 @@ 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)
|
username = chat.fixUsernameForIRC(username)
|
||||||
for _, value in self.clients.items():
|
for _, value in self.clients.items():
|
||||||
|
@ -610,10 +567,9 @@ 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)
|
fro = chat.fixUsernameForIRC(fro)
|
||||||
to = chat.fixUsernameForIRC(to)
|
to = chat.fixUsernameForIRC(to)
|
||||||
|
@ -629,26 +585,21 @@ class Server:
|
||||||
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:
|
if glob.sentry:
|
||||||
sentryClient = raven.Client(glob.conf.config["sentry"]["ircdsn"])
|
sentryClient = raven.Client(glob.conf.config["sentry"]["ircdns"])
|
||||||
|
|
||||||
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)
|
||||||
|
@ -679,7 +630,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 +649,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:
|
||||||
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()
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
# TODO: Rewrite this shit
|
# TODO: Rewrite this shit
|
||||||
from common import generalUtils
|
from common import generalUtils
|
||||||
from constants import serverPackets
|
|
||||||
from objects import glob
|
from objects import glob
|
||||||
from common.log import logUtils as log
|
|
||||||
|
|
||||||
|
|
||||||
class banchoConfig:
|
class banchoConfig:
|
||||||
|
@ -31,12 +29,7 @@ class banchoConfig:
|
||||||
"""
|
"""
|
||||||
self.config["banchoMaintenance"] = generalUtils.stringToBool(glob.db.fetch("SELECT value_int FROM bancho_settings WHERE name = 'bancho_maintenance'")["value_int"])
|
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["freeDirect"] = generalUtils.stringToBool(glob.db.fetch("SELECT value_int FROM bancho_settings WHERE name = 'free_direct'")["value_int"])
|
||||||
mainMenuIcon = glob.db.fetch("SELECT file_id, url FROM main_menu_icons WHERE is_current = 1 LIMIT 1")
|
self.config["menuIcon"] = glob.db.fetch("SELECT value_string FROM bancho_settings WHERE name = 'menu_icon'")["value_string"]
|
||||||
if mainMenuIcon is None:
|
|
||||||
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"]
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,20 +41,3 @@ class banchoConfig:
|
||||||
"""
|
"""
|
||||||
self.config["banchoMaintenance"] = maintenance
|
self.config["banchoMaintenance"] = maintenance
|
||||||
glob.db.execute("UPDATE bancho_settings SET value_int = %s WHERE name = 'bancho_maintenance'", [int(maintenance)])
|
glob.db.execute("UPDATE bancho_settings SET value_int = %s WHERE name = 'bancho_maintenance'", [int(maintenance)])
|
||||||
|
|
||||||
def reload(self):
|
|
||||||
# Reload settings from bancho_settings
|
|
||||||
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))
|
|
|
@ -1,19 +1,20 @@
|
||||||
import logging
|
|
||||||
|
|
||||||
from constants import exceptions
|
|
||||||
from objects import glob
|
from objects import glob
|
||||||
|
|
||||||
class channel:
|
class channel:
|
||||||
|
"""
|
||||||
|
A chat channel
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, name, description, publicRead, publicWrite, temp, hidden):
|
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.name = name
|
||||||
self.description = description
|
self.description = description
|
||||||
|
@ -21,24 +22,51 @@ class channel:
|
||||||
self.publicWrite = publicWrite
|
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)
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
from common.log import logUtils as log
|
from common.log import logUtils as log
|
||||||
from objects import channel
|
from objects import channel
|
||||||
from objects import glob
|
from objects import glob
|
||||||
from helpers import chatHelper as chat
|
|
||||||
|
|
||||||
|
|
||||||
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")
|
||||||
|
|
||||||
|
@ -25,17 +28,15 @@ class channelList:
|
||||||
|
|
||||||
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))
|
||||||
|
|
||||||
|
@ -44,46 +45,22 @@ class channelList:
|
||||||
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))
|
||||||
|
|
|
@ -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 = {}
|
||||||
|
|
||||||
|
@ -30,14 +19,6 @@ class chatFilters:
|
||||||
self.filters[lineSplit[0].lower()] = lineSplit[1].replace("\n", "")
|
self.filters[lineSplit[0].lower()] = 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(" ")
|
||||||
|
|
||||||
|
@ -51,4 +32,3 @@ class chatFilters:
|
||||||
|
|
||||||
# Return filtered message
|
# Return filtered message
|
||||||
return message
|
return message
|
||||||
"""
|
|
||||||
|
|
|
@ -12,37 +12,30 @@ from objects import glob
|
||||||
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.streams.broadcast("main", serverPackets.userPanel(999))
|
||||||
glob.streams.broadcast("main", serverPackets.userStats(999))
|
glob.streams.broadcast("main", serverPackets.userStats(999))
|
||||||
|
|
||||||
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 generalUtils.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
|
||||||
|
|
|
@ -11,17 +11,15 @@ 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"
|
DATADOG_PREFIX = "peppy"
|
||||||
BOT_NAME = "FokaBot"
|
|
||||||
application = None
|
application = None
|
||||||
db = None
|
db = None
|
||||||
redis = None
|
|
||||||
conf = None
|
conf = None
|
||||||
banchoConf = None
|
banchoConf = None
|
||||||
tokens = tokenList.tokenList()
|
tokens = tokenList.tokenList()
|
||||||
|
@ -33,6 +31,7 @@ schiavo = schiavo.schiavo()
|
||||||
dog = datadogClient.datadogClient()
|
dog = datadogClient.datadogClient()
|
||||||
verifiedCache = {}
|
verifiedCache = {}
|
||||||
chatFilters = None
|
chatFilters = None
|
||||||
|
userIDCache = {}
|
||||||
pool = None
|
pool = None
|
||||||
ircServer = None
|
ircServer = None
|
||||||
busyThreads = 0
|
busyThreads = 0
|
||||||
|
@ -49,11 +48,3 @@ restarting = False
|
||||||
startTime = int(time.time())
|
startTime = int(time.time())
|
||||||
|
|
||||||
streams = streamList.streamList()
|
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"
|
|
492
objects/match.py
492
objects/match.py
|
@ -1,9 +1,4 @@
|
||||||
import copy
|
import copy
|
||||||
import json
|
|
||||||
import threading
|
|
||||||
|
|
||||||
import time
|
|
||||||
|
|
||||||
from common.log import logUtils as log
|
from common.log import logUtils as log
|
||||||
from constants import dataTypes
|
from constants import dataTypes
|
||||||
from constants import matchModModes
|
from constants import matchModModes
|
||||||
|
@ -18,31 +13,29 @@ from objects import glob
|
||||||
|
|
||||||
class slot:
|
class slot:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.status = slotStatuses.FREE
|
self.status = slotStatuses.free
|
||||||
self.team = matchTeams.NO_TEAM
|
self.team = 0
|
||||||
self.userID = -1
|
self.userID = -1
|
||||||
self.user = None
|
self.user = None
|
||||||
self.mods = 0
|
self.mods = 0
|
||||||
self.loaded = False
|
self.loaded = False
|
||||||
self.skip = False
|
self.skip = False
|
||||||
self.complete = False
|
self.complete = False
|
||||||
self.score = 0
|
|
||||||
self.failed = False
|
|
||||||
self.passed = True
|
|
||||||
|
|
||||||
class match:
|
class match:
|
||||||
def __init__(self, matchID, matchName, matchPassword, beatmapID, beatmapName, beatmapMD5, gameMode, hostUserID, isTourney=False):
|
"""Multiplayer match object"""
|
||||||
|
def __init__(self, matchID, matchName, matchPassword, beatmapID, beatmapName, beatmapMD5, gameMode, hostUserID):
|
||||||
"""
|
"""
|
||||||
Create a new match object
|
Create a new match object
|
||||||
|
|
||||||
:param matchID: match progressive identifier
|
matchID -- match progressive identifier
|
||||||
:param matchName: match name, string
|
matchName -- match name, string
|
||||||
:param matchPassword: match md5 password. Leave empty for no password
|
matchPassword -- match md5 password. Leave empty for no password
|
||||||
:param beatmapID: beatmap ID
|
beatmapID -- beatmap ID
|
||||||
:param beatmapName: beatmap name, string
|
beatmapName -- beatmap name, string
|
||||||
:param beatmapMD5: beatmap md5 hash, string
|
beatmapMD5 -- beatmap md5 hash, string
|
||||||
:param gameMode: game mode ID. See gameModes.py
|
gameMode -- game mode ID. See gameModes.py
|
||||||
:param hostUserID: user id of the host
|
hostUserID -- user id of the host
|
||||||
"""
|
"""
|
||||||
self.matchID = matchID
|
self.matchID = matchID
|
||||||
self.streamName = "multi/{}".format(self.matchID)
|
self.streamName = "multi/{}".format(self.matchID)
|
||||||
|
@ -56,18 +49,11 @@ class match:
|
||||||
self.beatmapMD5 = beatmapMD5
|
self.beatmapMD5 = beatmapMD5
|
||||||
self.hostUserID = hostUserID
|
self.hostUserID = hostUserID
|
||||||
self.gameMode = gameMode
|
self.gameMode = gameMode
|
||||||
self.matchScoringType = matchScoringTypes.SCORE # default values
|
self.matchScoringType = matchScoringTypes.score # default values
|
||||||
self.matchTeamType = matchTeamTypes.HEAD_TO_HEAD # default value
|
self.matchTeamType = matchTeamTypes.headToHead # default value
|
||||||
self.matchModMode = matchModModes.NORMAL # default value
|
self.matchModMode = matchModModes.normal # default value
|
||||||
self.seed = 0
|
self.seed = 0
|
||||||
self.matchDataCache = bytes()
|
self.matchDataCache = bytes()
|
||||||
self.isTourney = isTourney
|
|
||||||
self.isLocked = False # if True, users can't change slots/teams. Used in tourney matches
|
|
||||||
self.isStarting = False
|
|
||||||
self._lock = threading.Lock()
|
|
||||||
self.createTime = int(time.time())
|
|
||||||
self.vinseID = None
|
|
||||||
self.bloodcatAlert = False
|
|
||||||
|
|
||||||
# Create all slots and reset them
|
# Create all slots and reset them
|
||||||
self.slots = []
|
self.slots = []
|
||||||
|
@ -79,67 +65,57 @@ class match:
|
||||||
glob.streams.add(self.playingStreamName)
|
glob.streams.add(self.playingStreamName)
|
||||||
|
|
||||||
# Create #multiplayer channel
|
# Create #multiplayer channel
|
||||||
glob.channels.addHiddenChannel("#multi_{}".format(self.matchID))
|
glob.channels.addTempChannel("#multi_{}".format(self.matchID))
|
||||||
log.info("MPROOM{}: {} match created!".format(self.matchID, "Tourney" if self.isTourney else "Normal"))
|
|
||||||
|
|
||||||
def getMatchData(self, censored = False):
|
def getMatchData(self):
|
||||||
"""
|
"""
|
||||||
Return binary match data structure for packetHelper
|
Return binary match data structure for packetHelper
|
||||||
Return binary match data structure for packetHelper
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
# General match info
|
# General match info
|
||||||
# TODO: Test without safe copy, the error might have been caused by outdated python bytecode cache
|
# TODO: Test without safe copy, the error might have been caused by outdated python bytecode cache
|
||||||
# safeMatch = copy.deepcopy(self)
|
safeMatch = copy.deepcopy(self)
|
||||||
struct = [
|
struct = [
|
||||||
[self.matchID, dataTypes.UINT16],
|
[safeMatch.matchID, dataTypes.UINT16],
|
||||||
[int(self.inProgress), dataTypes.BYTE],
|
[int(safeMatch.inProgress), dataTypes.BYTE],
|
||||||
[0, dataTypes.BYTE],
|
[0, dataTypes.BYTE],
|
||||||
[self.mods, dataTypes.UINT32],
|
[safeMatch.mods, dataTypes.UINT32],
|
||||||
[self.matchName, dataTypes.STRING]
|
[safeMatch.matchName, dataTypes.STRING],
|
||||||
|
[safeMatch.matchPassword, dataTypes.STRING],
|
||||||
|
[safeMatch.beatmapName, dataTypes.STRING],
|
||||||
|
[safeMatch.beatmapID, dataTypes.UINT32],
|
||||||
|
[safeMatch.beatmapMD5, dataTypes.STRING],
|
||||||
]
|
]
|
||||||
if censored and self.matchPassword:
|
|
||||||
struct.append(["redacted", dataTypes.STRING])
|
|
||||||
else:
|
|
||||||
struct.append([self.matchPassword, dataTypes.STRING])
|
|
||||||
|
|
||||||
struct.extend([
|
|
||||||
[self.beatmapName, dataTypes.STRING],
|
|
||||||
[self.beatmapID, dataTypes.UINT32],
|
|
||||||
[self.beatmapMD5, dataTypes.STRING]
|
|
||||||
])
|
|
||||||
|
|
||||||
# Slots status IDs, always 16 elements
|
# Slots status IDs, always 16 elements
|
||||||
for i in range(0,16):
|
for i in range(0,16):
|
||||||
struct.append([self.slots[i].status, dataTypes.BYTE])
|
struct.append([safeMatch.slots[i].status, dataTypes.BYTE])
|
||||||
|
|
||||||
# Slot teams, always 16 elements
|
# Slot teams, always 16 elements
|
||||||
for i in range(0,16):
|
for i in range(0,16):
|
||||||
struct.append([self.slots[i].team, dataTypes.BYTE])
|
struct.append([safeMatch.slots[i].team, dataTypes.BYTE])
|
||||||
|
|
||||||
# Slot user ID. Write only if slot is occupied
|
# Slot user ID. Write only if slot is occupied
|
||||||
for i in range(0,16):
|
for i in range(0,16):
|
||||||
if self.slots[i].user is not None and self.slots[i].user in glob.tokens.tokens:
|
if safeMatch.slots[i].user is not None and safeMatch.slots[i].user in glob.tokens.tokens:
|
||||||
struct.append([glob.tokens.tokens[self.slots[i].user].userID, dataTypes.UINT32])
|
struct.append([glob.tokens.tokens[safeMatch.slots[i].user].userID, dataTypes.UINT32])
|
||||||
|
|
||||||
# Other match data
|
# Other match data
|
||||||
struct.extend([
|
struct.extend([
|
||||||
[self.hostUserID, dataTypes.SINT32],
|
[safeMatch.hostUserID, dataTypes.SINT32],
|
||||||
[self.gameMode, dataTypes.BYTE],
|
[safeMatch.gameMode, dataTypes.BYTE],
|
||||||
[self.matchScoringType, dataTypes.BYTE],
|
[safeMatch.matchScoringType, dataTypes.BYTE],
|
||||||
[self.matchTeamType, dataTypes.BYTE],
|
[safeMatch.matchTeamType, dataTypes.BYTE],
|
||||||
[self.matchModMode, dataTypes.BYTE],
|
[safeMatch.matchModMode, dataTypes.BYTE],
|
||||||
])
|
])
|
||||||
|
|
||||||
# Slot mods if free mod is enabled
|
# Slot mods if free mod is enabled
|
||||||
if self.matchModMode == matchModModes.FREE_MOD:
|
if safeMatch.matchModMode == matchModModes.freeMod:
|
||||||
for i in range(0,16):
|
for i in range(0,16):
|
||||||
struct.append([self.slots[i].mods, dataTypes.UINT32])
|
struct.append([safeMatch.slots[i].mods, dataTypes.UINT32])
|
||||||
|
|
||||||
# Seed idk
|
# Seed idk
|
||||||
# TODO: Implement this, it should be used for mania "random" mod
|
# TODO: Implement this, it should be used for mania "random" mod
|
||||||
struct.append([self.seed, dataTypes.UINT32])
|
struct.append([safeMatch.seed, dataTypes.UINT32])
|
||||||
|
|
||||||
return struct
|
return struct
|
||||||
|
|
||||||
|
@ -147,44 +123,19 @@ class match:
|
||||||
"""
|
"""
|
||||||
Set room host to newHost and send him host packet
|
Set room host to newHost and send him host packet
|
||||||
|
|
||||||
:param newHost: new host userID
|
newHost -- new host userID
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
slotID = self.getUserSlotID(newHost)
|
slotID = self.getUserSlotID(newHost)
|
||||||
if slotID is None or self.slots[slotID].user not in glob.tokens.tokens:
|
if slotID is None or self.slots[slotID].user not in glob.tokens.tokens:
|
||||||
return False
|
return
|
||||||
token = glob.tokens.tokens[self.slots[slotID].user]
|
token = glob.tokens.tokens[self.slots[slotID].user]
|
||||||
self.hostUserID = newHost
|
self.hostUserID = newHost
|
||||||
token.enqueue(serverPackets.matchTransferHost())
|
token.enqueue(serverPackets.matchTransferHost())
|
||||||
self.sendUpdates()
|
self.sendUpdates()
|
||||||
log.info("MPROOM{}: {} is now the host".format(self.matchID, token.username))
|
log.info("MPROOM{}: {} is now the host".format(self.matchID, token.username))
|
||||||
return True
|
|
||||||
|
|
||||||
def removeHost(self):
|
|
||||||
"""
|
|
||||||
Removes the host (for tourney matches)
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
self.hostUserID = -1
|
|
||||||
self.sendUpdates()
|
|
||||||
log.info("MPROOM{}: Removed host".format(self.matchID))
|
|
||||||
|
|
||||||
def setSlot(self, slotID, status = None, team = None, user = "", mods = None, loaded = None, skip = None, complete = None):
|
def setSlot(self, slotID, status = None, team = None, user = "", mods = None, loaded = None, skip = None, complete = None):
|
||||||
"""
|
#self.setSlot(i, slotStatuses.notReady, 0, user, 0)
|
||||||
Set data for a specific slot.
|
|
||||||
All fields but slotID are optional.
|
|
||||||
Skipped fields won't be edited.
|
|
||||||
|
|
||||||
:param slotID: slot ID
|
|
||||||
:param status: new status
|
|
||||||
:param team: new team
|
|
||||||
:param user: new user id
|
|
||||||
:param mods: new mods
|
|
||||||
:param loaded: new loaded status
|
|
||||||
:param skip: new skip value
|
|
||||||
:param complete: new completed value
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
if status is not None:
|
if status is not None:
|
||||||
self.slots[slotID].status = status
|
self.slots[slotID].status = status
|
||||||
|
|
||||||
|
@ -210,9 +161,8 @@ class match:
|
||||||
"""
|
"""
|
||||||
Set slotID mods. Same as calling setSlot and then sendUpdate
|
Set slotID mods. Same as calling setSlot and then sendUpdate
|
||||||
|
|
||||||
:param slotID: slot number
|
slotID -- slot number
|
||||||
:param mods: new mods
|
mods -- new mods
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
# Set new slot data and send update
|
# Set new slot data and send update
|
||||||
self.setSlot(slotID, mods=mods)
|
self.setSlot(slotID, mods=mods)
|
||||||
|
@ -224,36 +174,32 @@ class match:
|
||||||
Switch slotID ready/not ready status
|
Switch slotID ready/not ready status
|
||||||
Same as calling setSlot and then sendUpdate
|
Same as calling setSlot and then sendUpdate
|
||||||
|
|
||||||
:param slotID: slot number
|
slotID -- slot number
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
# Update ready status and setnd update
|
# Update ready status and setnd update
|
||||||
if self.slots[slotID].user is None or self.isStarting:
|
|
||||||
return
|
|
||||||
oldStatus = self.slots[slotID].status
|
oldStatus = self.slots[slotID].status
|
||||||
if oldStatus == slotStatuses.READY:
|
if oldStatus == slotStatuses.ready:
|
||||||
newStatus = slotStatuses.NOT_READY
|
newStatus = slotStatuses.notReady
|
||||||
else:
|
else:
|
||||||
newStatus = slotStatuses.READY
|
newStatus = slotStatuses.ready
|
||||||
self.setSlot(slotID, newStatus)
|
self.setSlot(slotID, newStatus)
|
||||||
self.sendUpdates()
|
self.sendUpdates()
|
||||||
log.info("MPROOM{}: Slot{} changed ready status to {}".format(self.matchID, slotID, self.slots[slotID].status))
|
log.info("MPROOM{}: Slot{} changed ready status to {}".format(self.matchID, slotID, self.slots[slotID].status))
|
||||||
|
|
||||||
def toggleSlotLocked(self, slotID):
|
def toggleSlotLock(self, slotID):
|
||||||
"""
|
"""
|
||||||
Lock a slot
|
Lock a slot
|
||||||
Same as calling setSlot and then sendUpdate
|
Same as calling setSlot and then sendUpdate
|
||||||
|
|
||||||
:param slotID: slot number
|
slotID -- slot number
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
# Check if slot is already locked
|
# Check if slot is already locked
|
||||||
if self.slots[slotID].status == slotStatuses.LOCKED:
|
if self.slots[slotID].status == slotStatuses.locked:
|
||||||
newStatus = slotStatuses.FREE
|
newStatus = slotStatuses.free
|
||||||
else:
|
else:
|
||||||
newStatus = slotStatuses.LOCKED
|
newStatus = slotStatuses.locked
|
||||||
|
|
||||||
# Send updated settings to kicked user, so they will return to the lobby.
|
# Send updated settings to kicked user, so he returns to lobby
|
||||||
if self.slots[slotID].user is not None and self.slots[slotID].user in glob.tokens.tokens:
|
if self.slots[slotID].user is not None and self.slots[slotID].user in glob.tokens.tokens:
|
||||||
glob.tokens.tokens[self.slots[slotID].user].enqueue(serverPackets.updateMatch(self.matchID))
|
glob.tokens.tokens[self.slots[slotID].user].enqueue(serverPackets.updateMatch(self.matchID))
|
||||||
|
|
||||||
|
@ -262,14 +208,13 @@ class match:
|
||||||
|
|
||||||
# Send updates to everyone else
|
# Send updates to everyone else
|
||||||
self.sendUpdates()
|
self.sendUpdates()
|
||||||
log.info("MPROOM{}: Slot{} {}".format(self.matchID, slotID, "locked" if newStatus == slotStatuses.LOCKED else "unlocked"))
|
log.info("MPROOM{}: Slot{} {}".format(self.matchID, slotID, "locked" if newStatus == slotStatuses.locked else "unlocked"))
|
||||||
|
|
||||||
def playerLoaded(self, userID):
|
def playerLoaded(self, userID):
|
||||||
"""
|
"""
|
||||||
Set a player loaded status to True
|
Set a player loaded status to True
|
||||||
|
|
||||||
:param userID: ID of user
|
userID -- ID of user
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
slotID = self.getUserSlotID(userID)
|
slotID = self.getUserSlotID(userID)
|
||||||
if slotID is None:
|
if slotID is None:
|
||||||
|
@ -283,7 +228,7 @@ class match:
|
||||||
total = 0
|
total = 0
|
||||||
loaded = 0
|
loaded = 0
|
||||||
for i in range(0,16):
|
for i in range(0,16):
|
||||||
if self.slots[i].status == slotStatuses.PLAYING:
|
if self.slots[i].status == slotStatuses.playing:
|
||||||
total+=1
|
total+=1
|
||||||
if self.slots[i].loaded:
|
if self.slots[i].loaded:
|
||||||
loaded+=1
|
loaded+=1
|
||||||
|
@ -292,11 +237,7 @@ class match:
|
||||||
self.allPlayersLoaded()
|
self.allPlayersLoaded()
|
||||||
|
|
||||||
def allPlayersLoaded(self):
|
def allPlayersLoaded(self):
|
||||||
"""
|
"""Send allPlayersLoaded packet to every playing usr in match"""
|
||||||
Send allPlayersLoaded packet to every playing usr in match
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
glob.streams.broadcast(self.playingStreamName, serverPackets.allPlayersLoaded())
|
glob.streams.broadcast(self.playingStreamName, serverPackets.allPlayersLoaded())
|
||||||
log.info("MPROOM{}: All players loaded! Match starting...".format(self.matchID))
|
log.info("MPROOM{}: All players loaded! Match starting...".format(self.matchID))
|
||||||
|
|
||||||
|
@ -304,8 +245,7 @@ class match:
|
||||||
"""
|
"""
|
||||||
Set a player skip status to True
|
Set a player skip status to True
|
||||||
|
|
||||||
:param userID: ID of user
|
userID -- ID of user
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
slotID = self.getUserSlotID(userID)
|
slotID = self.getUserSlotID(userID)
|
||||||
if slotID is None:
|
if slotID is None:
|
||||||
|
@ -323,7 +263,7 @@ class match:
|
||||||
total = 0
|
total = 0
|
||||||
skipped = 0
|
skipped = 0
|
||||||
for i in range(0,16):
|
for i in range(0,16):
|
||||||
if self.slots[i].status == slotStatuses.PLAYING:
|
if self.slots[i].status == slotStatuses.playing:
|
||||||
total+=1
|
total+=1
|
||||||
if self.slots[i].skip:
|
if self.slots[i].skip:
|
||||||
skipped+=1
|
skipped+=1
|
||||||
|
@ -332,39 +272,15 @@ class match:
|
||||||
self.allPlayersSkipped()
|
self.allPlayersSkipped()
|
||||||
|
|
||||||
def allPlayersSkipped(self):
|
def allPlayersSkipped(self):
|
||||||
"""
|
"""Send allPlayersSkipped packet to every playing usr in match"""
|
||||||
Send allPlayersSkipped packet to every playing usr in match
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
glob.streams.broadcast(self.playingStreamName, serverPackets.allPlayersSkipped())
|
glob.streams.broadcast(self.playingStreamName, serverPackets.allPlayersSkipped())
|
||||||
log.info("MPROOM{}: All players have skipped!".format(self.matchID))
|
log.info("MPROOM{}: All players have skipped!".format(self.matchID))
|
||||||
|
|
||||||
def updateScore(self, slotID, score):
|
|
||||||
"""
|
|
||||||
Update score for a slot
|
|
||||||
|
|
||||||
:param slotID: the slot that the user that is updating their score is in
|
|
||||||
:param score: the new score to update
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
self.slots[slotID].score = score
|
|
||||||
|
|
||||||
def updateHP(self, slotID, hp):
|
|
||||||
"""
|
|
||||||
Update HP for a slot
|
|
||||||
|
|
||||||
:param slotID: the slot that the user that is updating their hp is in
|
|
||||||
:param hp: the new hp to update
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
self.slots[slotID].failed = True if hp == 254 else False
|
|
||||||
|
|
||||||
def playerCompleted(self, userID):
|
def playerCompleted(self, userID):
|
||||||
"""
|
"""
|
||||||
Set userID's slot completed to True
|
Set userID's slot completed to True
|
||||||
|
|
||||||
:param userID: ID of user
|
userID -- ID of user
|
||||||
"""
|
"""
|
||||||
slotID = self.getUserSlotID(userID)
|
slotID = self.getUserSlotID(userID)
|
||||||
if slotID is None:
|
if slotID is None:
|
||||||
|
@ -378,7 +294,7 @@ class match:
|
||||||
total = 0
|
total = 0
|
||||||
completed = 0
|
completed = 0
|
||||||
for i in range(0,16):
|
for i in range(0,16):
|
||||||
if self.slots[i].status == slotStatuses.PLAYING:
|
if self.slots[i].status == slotStatuses.playing:
|
||||||
total+=1
|
total+=1
|
||||||
if self.slots[i].complete:
|
if self.slots[i].complete:
|
||||||
completed+=1
|
completed+=1
|
||||||
|
@ -387,40 +303,18 @@ class match:
|
||||||
self.allPlayersCompleted()
|
self.allPlayersCompleted()
|
||||||
|
|
||||||
def allPlayersCompleted(self):
|
def allPlayersCompleted(self):
|
||||||
"""
|
"""Cleanup match stuff and send match end packet to everyone"""
|
||||||
Cleanup match stuff and send match end packet to everyone
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
# Collect some info about the match that just ended to send to the api
|
|
||||||
infoToSend = {
|
|
||||||
"id": self.matchID,
|
|
||||||
"name": self.matchName,
|
|
||||||
"beatmap_id": self.beatmapID,
|
|
||||||
"mods": self.mods,
|
|
||||||
"game_mode": self.gameMode,
|
|
||||||
"scores": {}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Add score info for each player
|
|
||||||
for i in range(0,16):
|
|
||||||
if self.slots[i].user is not None and self.slots[i].status == slotStatuses.PLAYING:
|
|
||||||
infoToSend["scores"][glob.tokens.tokens[self.slots[i].user].userID] = {
|
|
||||||
"score": self.slots[i].score,
|
|
||||||
"mods": self.slots[i].mods,
|
|
||||||
"failed": self.slots[i].failed,
|
|
||||||
"pass": self.slots[i].passed,
|
|
||||||
"team": self.slots[i].team
|
|
||||||
}
|
|
||||||
|
|
||||||
# Send the info to the api
|
|
||||||
glob.redis.publish("api:mp_complete_match", json.dumps(infoToSend))
|
|
||||||
|
|
||||||
# Reset inProgress
|
# Reset inProgress
|
||||||
self.inProgress = False
|
self.inProgress = False
|
||||||
|
|
||||||
# Reset slots
|
# Reset slots
|
||||||
self.resetSlots()
|
for i in range(0,16):
|
||||||
|
if self.slots[i].user is not None and self.slots[i].status == slotStatuses.playing:
|
||||||
|
self.slots[i].status = slotStatuses.notReady
|
||||||
|
self.slots[i].loaded = False
|
||||||
|
self.slots[i].skip = False
|
||||||
|
self.slots[i].complete = False
|
||||||
|
|
||||||
# Send match update
|
# Send match update
|
||||||
self.sendUpdates()
|
self.sendUpdates()
|
||||||
|
@ -429,51 +323,16 @@ class match:
|
||||||
glob.streams.broadcast(self.streamName, serverPackets.matchComplete())
|
glob.streams.broadcast(self.streamName, serverPackets.matchComplete())
|
||||||
|
|
||||||
# Destroy playing stream
|
# Destroy playing stream
|
||||||
glob.streams.dispose(self.playingStreamName)
|
|
||||||
glob.streams.remove(self.playingStreamName)
|
glob.streams.remove(self.playingStreamName)
|
||||||
|
|
||||||
# Console output
|
# Console output
|
||||||
log.info("MPROOM{}: Match completed".format(self.matchID))
|
log.info("MPROOM{}: Match completed".format(self.matchID))
|
||||||
|
|
||||||
# Set vinse id if needed
|
|
||||||
chanName = "#multi_{}".format(self.matchID)
|
|
||||||
if self.vinseID is None:
|
|
||||||
self.vinseID = (int(time.time()) // (60 * 15)) << 32 | self.matchID
|
|
||||||
chat.sendMessage("FokaBot", chanName, "Match history available [{} here]".format(
|
|
||||||
"https://vinse.ripple.moe/match/{}".format(self.vinseID)
|
|
||||||
))
|
|
||||||
if not self.bloodcatAlert:
|
|
||||||
chat.sendMessage(
|
|
||||||
"FokaBot",
|
|
||||||
chanName,
|
|
||||||
"Oh by the way, in case you're playing unranked or broken maps "
|
|
||||||
"that are now available through ripple's osu!direct, you can "
|
|
||||||
"type '!bloodcat' in the chat to get a download link for the "
|
|
||||||
"currently selected map from Bloodcat!"
|
|
||||||
)
|
|
||||||
self.bloodcatAlert = True
|
|
||||||
|
|
||||||
# If this is a tournament match, then we send a notification in the chat
|
|
||||||
# saying that the match has completed.
|
|
||||||
if self.isTourney and (chanName in glob.channels.channels):
|
|
||||||
chat.sendMessage(glob.BOT_NAME, chanName, "Match has just finished.")
|
|
||||||
|
|
||||||
def resetSlots(self):
|
|
||||||
for i in range(0,16):
|
|
||||||
if self.slots[i].user is not None and self.slots[i].status == slotStatuses.PLAYING:
|
|
||||||
self.slots[i].status = slotStatuses.NOT_READY
|
|
||||||
self.slots[i].loaded = False
|
|
||||||
self.slots[i].skip = False
|
|
||||||
self.slots[i].complete = False
|
|
||||||
self.slots[i].score = 0
|
|
||||||
self.slots[i].failed = False
|
|
||||||
self.slots[i].passed = True
|
|
||||||
|
|
||||||
def getUserSlotID(self, userID):
|
def getUserSlotID(self, userID):
|
||||||
"""
|
"""
|
||||||
Get slot ID occupied by userID
|
Get slot ID occupied by userID
|
||||||
|
|
||||||
:return: slot id if found, None if user is not in room
|
return -- slot id if found, None if user is not in room
|
||||||
"""
|
"""
|
||||||
for i in range(0,16):
|
for i in range(0,16):
|
||||||
if self.slots[i].user is not None and self.slots[i].user in glob.tokens.tokens and glob.tokens.tokens[self.slots[i].user].userID == userID:
|
if self.slots[i].user is not None and self.slots[i].user in glob.tokens.tokens and glob.tokens.tokens[self.slots[i].user].userID == userID:
|
||||||
|
@ -484,23 +343,21 @@ class match:
|
||||||
"""
|
"""
|
||||||
Add someone to users in match
|
Add someone to users in match
|
||||||
|
|
||||||
:param user: user object of the user
|
userID -- user id of the user
|
||||||
:return: True if join success, False if fail (room is full)
|
return -- True if join success, False if fail (room is full)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Make sure we're not in this match
|
# Make sure we're not in this match
|
||||||
for i in range(0,16):
|
for i in range(0,16):
|
||||||
if self.slots[i].user == user.token:
|
if self.slots[i].user == user.token:
|
||||||
# Set bugged slot to free
|
# Set bugged slot to free
|
||||||
self.setSlot(i, slotStatuses.FREE, 0, None, 0)
|
self.setSlot(i, slotStatuses.free, 0, None, 0)
|
||||||
|
|
||||||
# Find first free slot
|
# Find first free slot
|
||||||
for i in range(0,16):
|
for i in range(0,16):
|
||||||
if self.slots[i].status == slotStatuses.FREE:
|
if self.slots[i].status == slotStatuses.free:
|
||||||
# Occupy slot
|
# Occupy slot
|
||||||
team = matchTeams.NO_TEAM
|
self.setSlot(i, slotStatuses.notReady, 0, user.token, 0)
|
||||||
if self.matchTeamType == matchTeamTypes.TEAM_VS or self.matchTeamType == matchTeamTypes.TAG_TEAM_VS:
|
|
||||||
team = matchTeams.RED if i % 2 == 0 else matchTeams.BLUE
|
|
||||||
self.setSlot(i, slotStatuses.NOT_READY, team, user.token, 0)
|
|
||||||
|
|
||||||
# Send updated match data
|
# Send updated match data
|
||||||
self.sendUpdates()
|
self.sendUpdates()
|
||||||
|
@ -511,13 +368,11 @@ class match:
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def userLeft(self, user, disposeMatch=True):
|
def userLeft(self, user):
|
||||||
"""
|
"""
|
||||||
Remove someone from users in match
|
Remove someone from users in match
|
||||||
|
|
||||||
:param user: user object of the user
|
userID -- user if of the user
|
||||||
:param disposeMatch: if `True`, will try to dispose match if there are no users in the room
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
# Make sure the user is in room
|
# Make sure the user is in room
|
||||||
slotID = self.getUserSlotID(user.userID)
|
slotID = self.getUserSlotID(user.userID)
|
||||||
|
@ -525,13 +380,13 @@ class match:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Set that slot to free
|
# Set that slot to free
|
||||||
self.setSlot(slotID, slotStatuses.FREE, 0, None, 0)
|
self.setSlot(slotID, slotStatuses.free, 0, None, 0)
|
||||||
|
|
||||||
# Check if everyone left
|
# Check if everyone left
|
||||||
if self.countUsers() == 0 and disposeMatch and not self.isTourney:
|
if self.countUsers() == 0:
|
||||||
# Dispose match
|
# Dispose match
|
||||||
glob.matches.disposeMatch(self.matchID)
|
glob.matches.disposeMatch(self.matchID)
|
||||||
log.info("MPROOM{}: Room disposed because all users left".format(self.matchID))
|
log.info("MPROOM{}: Room disposed".format(self.matchID))
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check if host left
|
# Check if host left
|
||||||
|
@ -552,29 +407,24 @@ class match:
|
||||||
"""
|
"""
|
||||||
Change userID slot to newSlotID
|
Change userID slot to newSlotID
|
||||||
|
|
||||||
:param userID: user that changed slot
|
userID -- user that changed slot
|
||||||
:param newSlotID: slot id of new slot
|
newSlotID -- slot id of new slot
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
# Make sure the match is not locked
|
|
||||||
if self.isLocked or self.isStarting:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Make sure the user is in room
|
# Make sure the user is in room
|
||||||
oldSlotID = self.getUserSlotID(userID)
|
oldSlotID = self.getUserSlotID(userID)
|
||||||
if oldSlotID is None:
|
if oldSlotID is None:
|
||||||
return False
|
return
|
||||||
|
|
||||||
# Make sure there is no one inside new slot
|
# Make sure there is no one inside new slot
|
||||||
if self.slots[newSlotID].user is not None or self.slots[newSlotID].status != slotStatuses.FREE:
|
if self.slots[newSlotID].user is not None and self.slots[newSlotID].status != slotStatuses.free:
|
||||||
return False
|
return
|
||||||
|
|
||||||
# Get old slot data
|
# Get old slot data
|
||||||
#oldData = dill.copy(self.slots[oldSlotID])
|
#oldData = dill.copy(self.slots[oldSlotID])
|
||||||
oldData = copy.deepcopy(self.slots[oldSlotID])
|
oldData = copy.deepcopy(self.slots[oldSlotID])
|
||||||
|
|
||||||
# Free old slot
|
# Free old slot
|
||||||
self.setSlot(oldSlotID, slotStatuses.FREE, 0, None, 0, False, False, False)
|
self.setSlot(oldSlotID, slotStatuses.free, 0, None, 0, False, False, False)
|
||||||
|
|
||||||
# Occupy new slot
|
# Occupy new slot
|
||||||
self.setSlot(newSlotID, oldData.status, oldData.team, oldData.user, oldData.mods)
|
self.setSlot(newSlotID, oldData.status, oldData.team, oldData.user, oldData.mods)
|
||||||
|
@ -584,16 +434,18 @@ class match:
|
||||||
|
|
||||||
# Console output
|
# Console output
|
||||||
log.info("MPROOM{}: {} moved to slot {}".format(self.matchID, userID, newSlotID))
|
log.info("MPROOM{}: {} moved to slot {}".format(self.matchID, userID, newSlotID))
|
||||||
return True
|
|
||||||
|
|
||||||
def changePassword(self, newPassword):
|
def changePassword(self, newPassword):
|
||||||
"""
|
"""
|
||||||
Change match password to newPassword
|
Change match password to newPassword
|
||||||
|
|
||||||
:param newPassword: new password string
|
newPassword -- new password string
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
self.matchPassword = newPassword
|
self.matchPassword = newPassword
|
||||||
|
#if newPassword != "":
|
||||||
|
# self.matchPassword = generalUtils.stringMd5(newPassword)
|
||||||
|
#else:
|
||||||
|
# self.matchPassword = ""
|
||||||
|
|
||||||
# Send password change to every user in match
|
# Send password change to every user in match
|
||||||
glob.streams.broadcast(self.streamName, serverPackets.changeMatchPassword(self.matchPassword))
|
glob.streams.broadcast(self.streamName, serverPackets.changeMatchPassword(self.matchPassword))
|
||||||
|
@ -608,8 +460,7 @@ class match:
|
||||||
"""
|
"""
|
||||||
Set match global mods
|
Set match global mods
|
||||||
|
|
||||||
:param mods: mods bitwise int thing
|
mods -- mods bitwise int thing
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
# Set new mods and send update
|
# Set new mods and send update
|
||||||
self.mods = mods
|
self.mods = mods
|
||||||
|
@ -620,9 +471,8 @@ class match:
|
||||||
"""
|
"""
|
||||||
Set no beatmap status for userID
|
Set no beatmap status for userID
|
||||||
|
|
||||||
:param userID: ID of user
|
userID -- ID of user
|
||||||
:param has: True if has beatmap, false if not
|
has -- True if has beatmap, false if not
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
# Make sure the user is in room
|
# Make sure the user is in room
|
||||||
slotID = self.getUserSlotID(userID)
|
slotID = self.getUserSlotID(userID)
|
||||||
|
@ -630,7 +480,7 @@ class match:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Set slot
|
# Set slot
|
||||||
self.setSlot(slotID, slotStatuses.NO_MAP if not has else slotStatuses.NOT_READY)
|
self.setSlot(slotID, slotStatuses.noMap if not has else slotStatuses.notReady)
|
||||||
|
|
||||||
# Send updates
|
# Send updates
|
||||||
self.sendUpdates()
|
self.sendUpdates()
|
||||||
|
@ -639,8 +489,7 @@ class match:
|
||||||
"""
|
"""
|
||||||
Transfer host to slotID
|
Transfer host to slotID
|
||||||
|
|
||||||
:param slotID: ID of slot
|
slotID -- ID of slot
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
# Make sure there is someone in that slot
|
# Make sure there is someone in that slot
|
||||||
if self.slots[slotID].user is None or self.slots[slotID].user not in glob.tokens.tokens:
|
if self.slots[slotID].user is None or self.slots[slotID].user not in glob.tokens.tokens:
|
||||||
|
@ -650,22 +499,19 @@ class match:
|
||||||
self.setHost(glob.tokens.tokens[self.slots[slotID].user].userID)
|
self.setHost(glob.tokens.tokens[self.slots[slotID].user].userID)
|
||||||
|
|
||||||
# Send updates
|
# Send updates
|
||||||
# self.sendUpdates()
|
self.sendUpdates()
|
||||||
|
|
||||||
def playerFailed(self, userID):
|
def playerFailed(self, userID):
|
||||||
"""
|
"""
|
||||||
Send userID's failed packet to everyone in match
|
Send userID's failed packet to everyone in match
|
||||||
|
|
||||||
:param userID: ID of user
|
userID -- ID of user
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
# Make sure the user is in room
|
# Make sure the user is in room
|
||||||
slotID = self.getUserSlotID(userID)
|
slotID = self.getUserSlotID(userID)
|
||||||
if slotID is None:
|
if slotID is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.slots[slotID].passed = False
|
|
||||||
|
|
||||||
# Send packet to everyone
|
# Send packet to everyone
|
||||||
glob.streams.broadcast(self.playingStreamName, serverPackets.playerFailed(slotID))
|
glob.streams.broadcast(self.playingStreamName, serverPackets.playerFailed(slotID))
|
||||||
|
|
||||||
|
@ -676,10 +522,10 @@ class match:
|
||||||
"""
|
"""
|
||||||
Fro invites to in this match.
|
Fro invites to in this match.
|
||||||
|
|
||||||
:param fro: sender userID
|
fro -- sender userID
|
||||||
:param to: receiver userID
|
to -- receiver userID
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Get tokens
|
# Get tokens
|
||||||
froToken = glob.tokens.getTokenFromUserID(fro)
|
froToken = glob.tokens.getTokenFromUserID(fro)
|
||||||
toToken = glob.tokens.getTokenFromUserID(to)
|
toToken = glob.tokens.getTokenFromUserID(to)
|
||||||
|
@ -688,7 +534,7 @@ class match:
|
||||||
|
|
||||||
# FokaBot is too busy
|
# FokaBot is too busy
|
||||||
if to == 999:
|
if to == 999:
|
||||||
chat.sendMessage(glob.BOT_NAME, froToken.username, "I would love to join your match, but I'm busy keeping ripple up and running. Sorry. Beep Boop.")
|
chat.sendMessage("FokaBot", froToken.username, "I would love to join your match, but I'm busy keeping ripple up and running. Sorry. Beep Boop.")
|
||||||
|
|
||||||
# Send message
|
# Send message
|
||||||
message = "Come join my multiplayer match: \"[osump://{}/{} {}]\"".format(self.matchID, self.matchPassword.replace(" ", "_"), self.matchName)
|
message = "Come join my multiplayer match: \"[osump://{}/{} {}]\"".format(self.matchID, self.matchPassword.replace(" ", "_"), self.matchName)
|
||||||
|
@ -698,7 +544,7 @@ class match:
|
||||||
"""
|
"""
|
||||||
Return how many players are in that match
|
Return how many players are in that match
|
||||||
|
|
||||||
:return: number of users
|
return -- number of users
|
||||||
"""
|
"""
|
||||||
c = 0
|
c = 0
|
||||||
for i in range(0,16):
|
for i in range(0,16):
|
||||||
|
@ -706,44 +552,27 @@ class match:
|
||||||
c+=1
|
c+=1
|
||||||
return c
|
return c
|
||||||
|
|
||||||
def changeTeam(self, userID, newTeam=None):
|
def changeTeam(self, userID):
|
||||||
"""
|
"""
|
||||||
Change userID's team
|
Change userID's team
|
||||||
|
|
||||||
:param userID: id of user
|
userID -- id of user
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
# Make sure this match's mode has teams
|
|
||||||
if self.matchTeamType != matchTeamTypes.TEAM_VS and self.matchTeamType != matchTeamTypes.TAG_TEAM_VS:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Make sure the match is not locked
|
|
||||||
if self.isLocked or self.isStarting:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Make sure the user is in room
|
# Make sure the user is in room
|
||||||
slotID = self.getUserSlotID(userID)
|
slotID = self.getUserSlotID(userID)
|
||||||
if slotID is None:
|
if slotID is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Update slot and send update
|
# Update slot and send update
|
||||||
if newTeam is None:
|
newTeam = matchTeams.blue if self.slots[slotID].team == matchTeams.red else matchTeams.red
|
||||||
newTeam = matchTeams.BLUE if self.slots[slotID].team == matchTeams.RED else matchTeams.RED
|
|
||||||
self.setSlot(slotID, None, newTeam)
|
self.setSlot(slotID, None, newTeam)
|
||||||
self.sendUpdates()
|
self.sendUpdates()
|
||||||
|
|
||||||
def sendUpdates(self):
|
def sendUpdates(self):
|
||||||
"""
|
|
||||||
Send match updates packet to everyone in lobby and room streams
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
self.matchDataCache = serverPackets.updateMatch(self.matchID)
|
self.matchDataCache = serverPackets.updateMatch(self.matchID)
|
||||||
censoredDataCache = serverPackets.updateMatch(self.matchID, censored=True)
|
|
||||||
if self.matchDataCache is not None:
|
if self.matchDataCache is not None:
|
||||||
glob.streams.broadcast(self.streamName, self.matchDataCache)
|
glob.streams.broadcast(self.streamName, self.matchDataCache)
|
||||||
if censoredDataCache is not None:
|
glob.streams.broadcast("lobby", self.matchDataCache)
|
||||||
glob.streams.broadcast("lobby", censoredDataCache)
|
|
||||||
else:
|
else:
|
||||||
log.error("MPROOM{}: Can't send match update packet, match data is None!!!".format(self.matchID))
|
log.error("MPROOM{}: Can't send match update packet, match data is None!!!".format(self.matchID))
|
||||||
|
|
||||||
|
@ -751,17 +580,16 @@ class match:
|
||||||
"""
|
"""
|
||||||
Check if match teams are valid
|
Check if match teams are valid
|
||||||
|
|
||||||
:return: True if valid, False if invalid
|
return -- True if valid, False if invalid
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
if self.matchTeamType != matchTeamTypes.TEAM_VS and self.matchTeamType != matchTeamTypes.TAG_TEAM_VS:
|
if self.matchTeamType != matchTeamTypes.teamVs or self.matchTeamType != matchTeamTypes.tagTeamVs:
|
||||||
# Teams are always valid if we have no teams
|
# Teams are always valid if we have no teams
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# We have teams, check if they are valid
|
# We have teams, check if they are valid
|
||||||
firstTeam = -1
|
firstTeam = -1
|
||||||
for i in range(0,16):
|
for i in range(0,16):
|
||||||
if self.slots[i].user is not None and (self.slots[i].status & slotStatuses.NO_MAP) == 0:
|
if self.slots[i].user is not None and (self.slots[i].status & slotStatuses.noMap) == 0:
|
||||||
if firstTeam == -1:
|
if firstTeam == -1:
|
||||||
firstTeam = self.slots[i].team
|
firstTeam = self.slots[i].team
|
||||||
elif firstTeam != self.slots[i].team:
|
elif firstTeam != self.slots[i].team:
|
||||||
|
@ -772,29 +600,21 @@ class match:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""
|
|
||||||
Start the match
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
# Remove isStarting timer flag thingie
|
|
||||||
self.isStarting = False
|
|
||||||
|
|
||||||
# Make sure we have enough players
|
# Make sure we have enough players
|
||||||
if self.countUsers() < 2 or not self.checkTeams():
|
if self.countUsers() < 2 or not self.checkTeams():
|
||||||
return False
|
return
|
||||||
|
|
||||||
# Create playing channel
|
# Create playing channel
|
||||||
glob.streams.add(self.playingStreamName)
|
glob.streams.add(self.playingStreamName)
|
||||||
|
|
||||||
# Change inProgress value
|
# Change inProgress value
|
||||||
self.inProgress = True
|
match.inProgress = True
|
||||||
|
|
||||||
# Set playing to ready players and set load, skip and complete to False
|
# Set playing to ready players and set load, skip and complete to False
|
||||||
# Make clients join playing stream
|
# Make clients join playing stream
|
||||||
for i in range(0, 16):
|
for i in range(0, 16):
|
||||||
if self.slots[i].user in glob.tokens.tokens:
|
if (self.slots[i].status & slotStatuses.ready) > 0 and self.slots[i].user in glob.tokens.tokens:
|
||||||
self.slots[i].status = slotStatuses.PLAYING
|
self.slots[i].status = slotStatuses.playing
|
||||||
self.slots[i].loaded = False
|
self.slots[i].loaded = False
|
||||||
self.slots[i].skip = False
|
self.slots[i].skip = False
|
||||||
self.slots[i].complete = False
|
self.slots[i].complete = False
|
||||||
|
@ -805,85 +625,3 @@ class match:
|
||||||
|
|
||||||
# Send updates
|
# Send updates
|
||||||
self.sendUpdates()
|
self.sendUpdates()
|
||||||
return True
|
|
||||||
|
|
||||||
def forceSize(self, matchSize):
|
|
||||||
for i in range(0, matchSize):
|
|
||||||
if self.slots[i].status == slotStatuses.LOCKED:
|
|
||||||
self.toggleSlotLocked(i)
|
|
||||||
for i in range(matchSize, 16):
|
|
||||||
if self.slots[i].status != slotStatuses.LOCKED:
|
|
||||||
self.toggleSlotLocked(i)
|
|
||||||
|
|
||||||
def abort(self):
|
|
||||||
if not self.inProgress:
|
|
||||||
log.warning("MPROOM{}: Match is not in progress!".format(self.matchID))
|
|
||||||
return
|
|
||||||
self.inProgress = False
|
|
||||||
self.isStarting = False
|
|
||||||
self.resetSlots()
|
|
||||||
self.sendUpdates()
|
|
||||||
glob.streams.broadcast(self.playingStreamName, serverPackets.matchAbort())
|
|
||||||
glob.streams.dispose(self.playingStreamName)
|
|
||||||
glob.streams.remove(self.playingStreamName)
|
|
||||||
log.info("MPROOM{}: Match aborted".format(self.matchID))
|
|
||||||
|
|
||||||
def initializeTeams(self):
|
|
||||||
if self.matchTeamType == matchTeamTypes.TEAM_VS or self.matchTeamType == matchTeamTypes.TAG_TEAM_VS:
|
|
||||||
# Set teams
|
|
||||||
for i, _slot in enumerate(self.slots):
|
|
||||||
_slot.team = matchTeams.RED if i % 2 == 0 else matchTeams.BLUE
|
|
||||||
else:
|
|
||||||
# Reset teams
|
|
||||||
for _slot in self.slots:
|
|
||||||
_slot.team = matchTeams.NO_TEAM
|
|
||||||
|
|
||||||
def resetMods(self):
|
|
||||||
for _slot in self.slots:
|
|
||||||
_slot.mods = 0
|
|
||||||
|
|
||||||
def resetReady(self):
|
|
||||||
for _slot in self.slots:
|
|
||||||
if _slot.status == slotStatuses.READY:
|
|
||||||
_slot.status = slotStatuses.NOT_READY
|
|
||||||
|
|
||||||
def sendReadyStatus(self):
|
|
||||||
chanName = "#multi_{}".format(self.matchID)
|
|
||||||
|
|
||||||
# Make sure match exists before attempting to do anything else
|
|
||||||
if chanName not in glob.channels.channels:
|
|
||||||
return
|
|
||||||
|
|
||||||
totalUsers = 0
|
|
||||||
readyUsers = 0
|
|
||||||
|
|
||||||
for slot in self.slots:
|
|
||||||
# Make sure there is a user in this slot
|
|
||||||
if slot.user is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# In this slot there is a user, so we increase the amount of total users
|
|
||||||
# in this multi room.
|
|
||||||
totalUsers += 1
|
|
||||||
|
|
||||||
if slot.status == slotStatuses.READY:
|
|
||||||
readyUsers += 1
|
|
||||||
|
|
||||||
message = "{} users ready out of {}.".format(readyUsers, totalUsers)
|
|
||||||
|
|
||||||
if totalUsers == readyUsers:
|
|
||||||
message += " All users ready!"
|
|
||||||
|
|
||||||
# Check whether there is anyone left in this match.
|
|
||||||
if totalUsers == 0:
|
|
||||||
message = "The match is now empty."
|
|
||||||
|
|
||||||
chat.sendMessage(glob.BOT_NAME, chanName, message)
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
# 🌚🌚🌚🌚🌚
|
|
||||||
self._lock.acquire()
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
||||||
self._lock.release()
|
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
|
|
||||||
from common.sentry import sentry
|
|
||||||
from constants.exceptions import periodicLoopException
|
|
||||||
from objects import match
|
from objects import match
|
||||||
from objects import glob
|
from objects import glob
|
||||||
from constants import serverPackets
|
from constants import serverPackets
|
||||||
|
@ -14,99 +9,39 @@ class matchList:
|
||||||
self.matches = {}
|
self.matches = {}
|
||||||
self.lastID = 1
|
self.lastID = 1
|
||||||
|
|
||||||
def createMatch(self, matchName, matchPassword, beatmapID, beatmapName, beatmapMD5, gameMode, hostUserID, isTourney=False):
|
def createMatch(self, matchName, matchPassword, beatmapID, beatmapName, beatmapMD5, gameMode, hostUserID):
|
||||||
"""
|
"""
|
||||||
Add a new match to matches list
|
Add a new match to matches list
|
||||||
|
|
||||||
:param matchName: match name, string
|
matchName -- match name, string
|
||||||
:param matchPassword: match md5 password. Leave empty for no password
|
matchPassword -- match md5 password. Leave empty for no password
|
||||||
:param beatmapID: beatmap ID
|
beatmapID -- beatmap ID
|
||||||
:param beatmapName: beatmap name, string
|
beatmapName -- beatmap name, string
|
||||||
:param beatmapMD5: beatmap md5 hash, string
|
beatmapMD5 -- beatmap md5 hash, string
|
||||||
:param gameMode: game mode ID. See gameModes.py
|
gameMode -- game mode ID. See gameModes.py
|
||||||
:param hostUserID: user id of who created the match
|
hostUserID -- user id of who created the match
|
||||||
:return: match ID
|
return -- match ID
|
||||||
"""
|
"""
|
||||||
# Add a new match to matches list and create its stream
|
# Add a new match to matches list and create its stream
|
||||||
matchID = self.lastID
|
matchID = self.lastID
|
||||||
self.lastID+=1
|
self.lastID+=1
|
||||||
self.matches[matchID] = match.match(matchID, matchName, matchPassword, beatmapID, beatmapName, beatmapMD5, gameMode, hostUserID, isTourney)
|
self.matches[matchID] = match.match(matchID, matchName, matchPassword, beatmapID, beatmapName, beatmapMD5, gameMode, hostUserID)
|
||||||
return matchID
|
return matchID
|
||||||
|
|
||||||
def disposeMatch(self, matchID):
|
def disposeMatch(self, matchID):
|
||||||
"""
|
"""
|
||||||
Destroy match object with id = matchID
|
Destroy match object with id = matchID
|
||||||
|
|
||||||
:param matchID: ID of match to dispose
|
matchID -- ID of match to dispose
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
# Make sure the match exists
|
# Make sure the match exists
|
||||||
if matchID not in self.matches:
|
if matchID not in self.matches:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get match and disconnect all players
|
# Remove match object and stream
|
||||||
_match = self.matches[matchID]
|
match = self.matches.pop(matchID)
|
||||||
for slot in _match.slots:
|
glob.streams.remove(match.streamName)
|
||||||
_token = glob.tokens.getTokenFromUserID(slot.userID, ignoreIRC=True)
|
glob.streams.remove(match.playingStreamName)
|
||||||
if _token is None:
|
|
||||||
continue
|
|
||||||
_match.userLeft(_token, disposeMatch=False) # don't dispose the match twice when we remove all players
|
|
||||||
|
|
||||||
# Delete chat channel
|
|
||||||
glob.channels.removeChannel("#multi_{}".format(_match.matchID))
|
|
||||||
|
|
||||||
# Send matchDisposed packet before disposing streams
|
|
||||||
glob.streams.broadcast(_match.streamName, serverPackets.disposeMatch(_match.matchID))
|
|
||||||
|
|
||||||
# Dispose all streams
|
|
||||||
glob.streams.dispose(_match.streamName)
|
|
||||||
glob.streams.dispose(_match.playingStreamName)
|
|
||||||
glob.streams.remove(_match.streamName)
|
|
||||||
glob.streams.remove(_match.playingStreamName)
|
|
||||||
|
|
||||||
# Send match dispose packet to everyone in lobby
|
# Send match dispose packet to everyone in lobby
|
||||||
glob.streams.broadcast("lobby", serverPackets.disposeMatch(matchID))
|
glob.streams.broadcast("lobby", serverPackets.disposeMatch(matchID))
|
||||||
del self.matches[matchID]
|
|
||||||
log.info("MPROOM{}: Room disposed manually".format(_match.matchID))
|
|
||||||
|
|
||||||
@sentry.capture()
|
|
||||||
def cleanupLoop(self):
|
|
||||||
"""
|
|
||||||
Start match cleanup loop.
|
|
||||||
Empty matches that have been created more than 60 seconds ago will get deleted.
|
|
||||||
Useful when people create useless lobbies with `!mp make`.
|
|
||||||
The check is done every 30 seconds.
|
|
||||||
This method starts an infinite loop, call it only once!
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
log.debug("Checking empty matches")
|
|
||||||
t = int(time.time())
|
|
||||||
emptyMatches = []
|
|
||||||
exceptions = []
|
|
||||||
|
|
||||||
# Collect all empty matches
|
|
||||||
for key, m in self.matches.items():
|
|
||||||
if [x for x in m.slots if x.user is not None]:
|
|
||||||
continue
|
|
||||||
if t - m.createTime >= 120:
|
|
||||||
log.debug("Match #{} marked for cleanup".format(m.matchID))
|
|
||||||
emptyMatches.append(m.matchID)
|
|
||||||
|
|
||||||
# Dispose all empty matches
|
|
||||||
for matchID in emptyMatches:
|
|
||||||
try:
|
|
||||||
self.disposeMatch(matchID)
|
|
||||||
except Exception as e:
|
|
||||||
exceptions.append(e)
|
|
||||||
log.error(
|
|
||||||
"Something wrong happened while disposing a timed out match. Reporting to Sentry when "
|
|
||||||
"the loop ends."
|
|
||||||
)
|
|
||||||
|
|
||||||
# Re-raise exception if needed
|
|
||||||
if exceptions:
|
|
||||||
raise periodicLoopException(exceptions)
|
|
||||||
finally:
|
|
||||||
# Schedule a new check (endless loop)
|
|
||||||
threading.Timer(30, self.cleanupLoop).start()
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import uuid
|
||||||
from common.constants import gameModes, actions
|
from common.constants import gameModes, actions
|
||||||
from common.log import logUtils as log
|
from common.log import logUtils as log
|
||||||
from common.ripple import userUtils
|
from common.ripple import userUtils
|
||||||
from constants import exceptions
|
|
||||||
from constants import serverPackets
|
from constants import serverPackets
|
||||||
from events import logoutEvent
|
from events import logoutEvent
|
||||||
from helpers import chatHelper as chat
|
from helpers import chatHelper as chat
|
||||||
|
@ -13,35 +12,31 @@ from objects import glob
|
||||||
|
|
||||||
|
|
||||||
class token:
|
class token:
|
||||||
|
|
||||||
def __init__(self, userID, token_ = None, ip ="", irc = False, timeOffset = 0, tournament = False):
|
def __init__(self, userID, token_ = None, ip ="", irc = False, timeOffset = 0, tournament = False):
|
||||||
"""
|
"""
|
||||||
Create a token object and set userID and token
|
Create a token object and set userID and token
|
||||||
|
|
||||||
:param userID: user associated to this token
|
userID -- user associated to this token
|
||||||
:param token_: if passed, set token to that value
|
token -- if passed, set token to that value
|
||||||
if not passed, token will be generated
|
if not passed, token will be generated
|
||||||
:param ip: client ip. optional.
|
ip -- client ip. optional.
|
||||||
:param irc: if True, set this token as IRC client. Default: False.
|
irc -- if True, set this token as IRC client. optional.
|
||||||
:param timeOffset: the time offset from UTC for this user. Default: 0.
|
timeOffset -- the time offset from UTC for this user. optional.
|
||||||
:param tournament: if True, flag this client as a tournement client. Default: True.
|
|
||||||
"""
|
"""
|
||||||
# Set stuff
|
# Set stuff
|
||||||
self.userID = userID
|
self.userID = userID
|
||||||
self.username = userUtils.getUsername(self.userID)
|
self.username = userUtils.getUsername(self.userID)
|
||||||
self.safeUsername = userUtils.getSafeUsername(self.userID)
|
|
||||||
self.privileges = userUtils.getPrivileges(self.userID)
|
self.privileges = userUtils.getPrivileges(self.userID)
|
||||||
self.admin = userUtils.isInPrivilegeGroup(self.userID, "developer")\
|
self.admin = userUtils.isInPrivilegeGroup(self.userID, "developer") or userUtils.isInPrivilegeGroup(self.userID, "community manager")
|
||||||
or userUtils.isInPrivilegeGroup(self.userID, "community manager")\
|
|
||||||
or userUtils.isInPrivilegeGroup(self.userID, "chat mod")
|
|
||||||
self.irc = irc
|
self.irc = irc
|
||||||
self.kicked = False
|
self.restricted = userUtils.isRestricted(priv=self.privileges)
|
||||||
self.restricted = userUtils.isRestricted(self.userID)
|
|
||||||
self.loginTime = int(time.time())
|
self.loginTime = int(time.time())
|
||||||
self.pingTime = self.loginTime
|
self.pingTime = self.loginTime
|
||||||
self.timeOffset = timeOffset
|
self.timeOffset = timeOffset
|
||||||
|
self.lock = threading.Lock() # Sync primitive
|
||||||
self.streams = []
|
self.streams = []
|
||||||
self.tournament = tournament
|
self.tournament = tournament
|
||||||
self.messagesBuffer = []
|
|
||||||
|
|
||||||
# Default variables
|
# Default variables
|
||||||
self.spectators = []
|
self.spectators = []
|
||||||
|
@ -85,11 +80,6 @@ class token:
|
||||||
else:
|
else:
|
||||||
self.token = str(uuid.uuid4())
|
self.token = str(uuid.uuid4())
|
||||||
|
|
||||||
# Locks
|
|
||||||
self.processingLock = threading.Lock() # Acquired while there's an incoming packet from this user
|
|
||||||
self._bufferLock = threading.Lock() # Acquired while writing to packets buffer
|
|
||||||
self._spectLock = threading.RLock()
|
|
||||||
|
|
||||||
# Set stats
|
# Set stats
|
||||||
self.updateCachedStats()
|
self.updateCachedStats()
|
||||||
|
|
||||||
|
@ -104,72 +94,51 @@ class token:
|
||||||
"""
|
"""
|
||||||
Add bytes (packets) to queue
|
Add bytes (packets) to queue
|
||||||
|
|
||||||
:param bytes_: (packet) bytes to enqueue
|
bytes -- (packet) bytes to enqueue
|
||||||
"""
|
"""
|
||||||
try:
|
if not self.irc:
|
||||||
# Acquire the buffer lock
|
|
||||||
self._bufferLock.acquire()
|
|
||||||
|
|
||||||
# Never enqueue for IRC clients or Foka
|
|
||||||
if self.irc or self.userID < 999:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Avoid memory leaks
|
|
||||||
if len(bytes_) < 10 * 10 ** 6:
|
if len(bytes_) < 10 * 10 ** 6:
|
||||||
self.queue += bytes_
|
self.queue += bytes_
|
||||||
else:
|
else:
|
||||||
log.warning("{}'s packets buffer is above 10M!! Lost some data!".format(self.username))
|
log.warning("{}'s packets buffer is above 10M!! Lost some data!".format(self.username))
|
||||||
finally:
|
|
||||||
# Release the buffer lock
|
|
||||||
self._bufferLock.release()
|
|
||||||
|
|
||||||
def resetQueue(self):
|
def resetQueue(self):
|
||||||
"""Resets the queue. Call when enqueued packets have been sent"""
|
"""Resets the queue. Call when enqueued packets have been sent"""
|
||||||
try:
|
self.queue = bytes()
|
||||||
self._bufferLock.acquire()
|
|
||||||
self.queue = bytes()
|
|
||||||
finally:
|
|
||||||
self._bufferLock.release()
|
|
||||||
|
|
||||||
def joinChannel(self, channelObject):
|
|
||||||
|
def joinChannel(self, channel):
|
||||||
"""
|
"""
|
||||||
Join a channel
|
Add channel to joined channels list
|
||||||
|
|
||||||
:param channelObject: channel object
|
channel -- channel name
|
||||||
:raises: exceptions.userAlreadyInChannelException()
|
|
||||||
exceptions.channelNoPermissionsException()
|
|
||||||
"""
|
"""
|
||||||
if channelObject.name in self.joinedChannels:
|
if channel not in self.joinedChannels:
|
||||||
raise exceptions.userAlreadyInChannelException()
|
self.joinedChannels.append(channel)
|
||||||
if not channelObject.publicRead and not self.admin:
|
|
||||||
raise exceptions.channelNoPermissionsException()
|
|
||||||
self.joinedChannels.append(channelObject.name)
|
|
||||||
self.joinStream("chat/{}".format(channelObject.name))
|
|
||||||
self.enqueue(serverPackets.channelJoinSuccess(self.userID, channelObject.clientName))
|
|
||||||
|
|
||||||
def partChannel(self, channelObject):
|
def partChannel(self, channel):
|
||||||
"""
|
"""
|
||||||
Remove channel from joined channels list
|
Remove channel from joined channels list
|
||||||
|
|
||||||
:param channelObject: channel object
|
channel -- channel name
|
||||||
"""
|
"""
|
||||||
self.joinedChannels.remove(channelObject.name)
|
if channel in self.joinedChannels:
|
||||||
self.leaveStream("chat/{}".format(channelObject.name))
|
self.joinedChannels.remove(channel)
|
||||||
|
|
||||||
def setLocation(self, latitude, longitude):
|
def setLocation(self, location):
|
||||||
"""
|
"""
|
||||||
Set client location
|
Set location (latitude and longitude)
|
||||||
|
|
||||||
:param latitude: latitude
|
location -- [latitude, longitude]
|
||||||
:param longitude: longitude
|
|
||||||
"""
|
"""
|
||||||
self.location = (latitude, longitude)
|
self.location = location
|
||||||
|
|
||||||
def getLatitude(self):
|
def getLatitude(self):
|
||||||
"""
|
"""
|
||||||
Get latitude
|
Get latitude
|
||||||
|
|
||||||
:return: latitude
|
return -- latitude
|
||||||
"""
|
"""
|
||||||
return self.location[0]
|
return self.location[0]
|
||||||
|
|
||||||
|
@ -177,123 +146,122 @@ class token:
|
||||||
"""
|
"""
|
||||||
Get longitude
|
Get longitude
|
||||||
|
|
||||||
:return: longitude
|
return -- longitude
|
||||||
"""
|
"""
|
||||||
return self.location[1]
|
return self.location[1]
|
||||||
|
|
||||||
def startSpectating(self, host):
|
def startSpectating(self, host):
|
||||||
"""
|
"""
|
||||||
Set the spectating user to userID, join spectator stream and chat channel
|
Set the spectating user to userID
|
||||||
and send required packets to host
|
|
||||||
|
|
||||||
:param host: host osuToken object
|
user -- user object
|
||||||
"""
|
"""
|
||||||
try:
|
# Stop spectating old client
|
||||||
self._spectLock.acquire()
|
self.stopSpectating()
|
||||||
|
|
||||||
# Stop spectating old client
|
# Set new spectator host
|
||||||
self.stopSpectating()
|
self.spectating = host.token
|
||||||
|
self.spectatingUserID = host.userID
|
||||||
|
|
||||||
# Set new spectator host
|
# Add us to host's spectator list
|
||||||
self.spectating = host.token
|
host.spectators.append(self.token)
|
||||||
self.spectatingUserID = host.userID
|
|
||||||
|
|
||||||
# Add us to host's spectator list
|
# Create and join spectator stream
|
||||||
host.spectators.append(self.token)
|
streamName = "spect/{}".format(host.userID)
|
||||||
|
glob.streams.add(streamName)
|
||||||
|
self.joinStream(streamName)
|
||||||
|
host.joinStream(streamName)
|
||||||
|
|
||||||
# Create and join spectator stream
|
# Send spectator join packet to host
|
||||||
streamName = "spect/{}".format(host.userID)
|
host.enqueue(serverPackets.addSpectator(self.userID))
|
||||||
glob.streams.add(streamName)
|
|
||||||
self.joinStream(streamName)
|
|
||||||
host.joinStream(streamName)
|
|
||||||
|
|
||||||
# Send spectator join packet to host
|
# Create and join #spectator (#spect_userid) channel
|
||||||
host.enqueue(serverPackets.addSpectator(self.userID))
|
glob.channels.addTempChannel("#spect_{}".format(host.userID))
|
||||||
|
chat.joinChannel(token=self, channel="#spect_{}".format(host.userID))
|
||||||
|
if len(host.spectators) == 1:
|
||||||
|
# First spectator, send #spectator join to host too
|
||||||
|
chat.joinChannel(token=host, channel="#spect_{}".format(host.userID))
|
||||||
|
|
||||||
# Create and join #spectator (#spect_userid) channel
|
# Send fellow spectator join to all clients
|
||||||
glob.channels.addTempChannel("#spect_{}".format(host.userID))
|
glob.streams.broadcast(streamName, serverPackets.fellowSpectatorJoined(self.userID))
|
||||||
chat.joinChannel(token=self, channel="#spect_{}".format(host.userID), force=True)
|
|
||||||
if len(host.spectators) == 1:
|
|
||||||
# First spectator, send #spectator join to host too
|
|
||||||
chat.joinChannel(token=host, channel="#spect_{}".format(host.userID), force=True)
|
|
||||||
|
|
||||||
# Send fellow spectator join to all clients
|
# Get current spectators list
|
||||||
glob.streams.broadcast(streamName, serverPackets.fellowSpectatorJoined(self.userID))
|
for i in host.spectators:
|
||||||
|
if i != self.token and i in glob.tokens.tokens:
|
||||||
|
self.enqueue(serverPackets.fellowSpectatorJoined(glob.tokens.tokens[i].userID))
|
||||||
|
|
||||||
# Get current spectators list
|
# Log
|
||||||
for i in host.spectators:
|
log.info("{} is spectating {}".format(self.username, host.username))
|
||||||
if i != self.token and i in glob.tokens.tokens:
|
|
||||||
self.enqueue(serverPackets.fellowSpectatorJoined(glob.tokens.tokens[i].userID))
|
|
||||||
|
|
||||||
# Log
|
|
||||||
log.info("{} is spectating {}".format(self.username, host.username))
|
|
||||||
finally:
|
|
||||||
self._spectLock.release()
|
|
||||||
|
|
||||||
def stopSpectating(self):
|
def stopSpectating(self):
|
||||||
|
# Remove our userID from host's spectators
|
||||||
|
if self.spectating is None:
|
||||||
|
return
|
||||||
|
if self.spectating in glob.tokens.tokens:
|
||||||
|
hostToken = glob.tokens.tokens[self.spectating]
|
||||||
|
else:
|
||||||
|
hostToken = None
|
||||||
|
streamName = "spect/{}".format(self.spectatingUserID)
|
||||||
|
|
||||||
|
# Remove us from host's spectators list,
|
||||||
|
# leave spectator stream
|
||||||
|
# and end the spectator left packet to host
|
||||||
|
self.leaveStream(streamName)
|
||||||
|
if hostToken is not None:
|
||||||
|
hostToken.spectators.remove(self.token)
|
||||||
|
hostToken.enqueue(serverPackets.removeSpectator(self.userID))
|
||||||
|
|
||||||
|
# and to all other spectators
|
||||||
|
for i in hostToken.spectators:
|
||||||
|
if i in glob.tokens.tokens:
|
||||||
|
glob.tokens.tokens[i].enqueue(serverPackets.fellowSpectatorLeft(self.userID))
|
||||||
|
|
||||||
|
# If nobody is spectating the host anymore, close #spectator channel
|
||||||
|
# and remove host from spect stream too
|
||||||
|
if len(hostToken.spectators) == 0:
|
||||||
|
chat.partChannel(token=hostToken, channel="#spect_{}".format(hostToken.userID), kick=True)
|
||||||
|
hostToken.leaveStream(streamName)
|
||||||
|
|
||||||
|
# Part #spectator channel
|
||||||
|
chat.partChannel(token=self, channel="#spect_{}".format(self.spectatingUserID), kick=True)
|
||||||
|
|
||||||
|
# Console output
|
||||||
|
log.info("{} is no longer spectating {}".format(self.username, self.spectatingUserID))
|
||||||
|
|
||||||
|
# Set our spectating user to 0
|
||||||
|
self.spectating = None
|
||||||
|
self.spectatingUserID = 0
|
||||||
|
|
||||||
|
def setCountry(self, countryID):
|
||||||
"""
|
"""
|
||||||
Stop spectating, leave spectator stream and channel
|
Set country to countryID
|
||||||
and send required packets to host
|
|
||||||
|
|
||||||
:return:
|
countryID -- numeric country ID. See countryHelper.py
|
||||||
"""
|
"""
|
||||||
try:
|
self.country = countryID
|
||||||
self._spectLock.acquire()
|
|
||||||
|
|
||||||
# Remove our userID from host's spectators
|
def getCountry(self):
|
||||||
if self.spectating is None or self.spectatingUserID <= 0:
|
"""
|
||||||
return
|
Get numeric country ID
|
||||||
if self.spectating in glob.tokens.tokens:
|
|
||||||
hostToken = glob.tokens.tokens[self.spectating]
|
|
||||||
else:
|
|
||||||
hostToken = None
|
|
||||||
streamName = "spect/{}".format(self.spectatingUserID)
|
|
||||||
|
|
||||||
# Remove us from host's spectators list,
|
return -- numeric country ID. See countryHelper.py
|
||||||
# leave spectator stream
|
"""
|
||||||
# and end the spectator left packet to host
|
return self.country
|
||||||
self.leaveStream(streamName)
|
|
||||||
if hostToken is not None:
|
|
||||||
hostToken.spectators.remove(self.token)
|
|
||||||
hostToken.enqueue(serverPackets.removeSpectator(self.userID))
|
|
||||||
|
|
||||||
# and to all other spectators
|
|
||||||
for i in hostToken.spectators:
|
|
||||||
if i in glob.tokens.tokens:
|
|
||||||
glob.tokens.tokens[i].enqueue(serverPackets.fellowSpectatorLeft(self.userID))
|
|
||||||
|
|
||||||
# If nobody is spectating the host anymore, close #spectator channel
|
|
||||||
# and remove host from spect stream too
|
|
||||||
if len(hostToken.spectators) == 0:
|
|
||||||
chat.partChannel(token=hostToken, channel="#spect_{}".format(hostToken.userID), kick=True, force=True)
|
|
||||||
hostToken.leaveStream(streamName)
|
|
||||||
|
|
||||||
# Console output
|
|
||||||
log.info("{} is no longer spectating {}. Current spectators: {}".format(self.username, self.spectatingUserID, hostToken.spectators))
|
|
||||||
|
|
||||||
# Part #spectator channel
|
|
||||||
chat.partChannel(token=self, channel="#spect_{}".format(self.spectatingUserID), kick=True, force=True)
|
|
||||||
|
|
||||||
# Set our spectating user to 0
|
|
||||||
self.spectating = None
|
|
||||||
self.spectatingUserID = 0
|
|
||||||
finally:
|
|
||||||
self._spectLock.release()
|
|
||||||
|
|
||||||
def updatePingTime(self):
|
def updatePingTime(self):
|
||||||
"""
|
"""Update latest ping time"""
|
||||||
Update latest ping time to current time
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
self.pingTime = int(time.time())
|
self.pingTime = int(time.time())
|
||||||
|
|
||||||
|
def setAwayMessage(self, __awayMessage):
|
||||||
|
"""Set a new away message"""
|
||||||
|
self.awayMessage = __awayMessage
|
||||||
|
|
||||||
|
|
||||||
def joinMatch(self, matchID):
|
def joinMatch(self, matchID):
|
||||||
"""
|
"""
|
||||||
Set match to matchID, join match stream and channel
|
Set match to matchID, join match stream and channel
|
||||||
|
|
||||||
:param matchID: new match ID
|
matchID -- new match ID
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
# Make sure the match exists
|
# Make sure the match exists
|
||||||
if matchID not in glob.matches.matches:
|
if matchID not in glob.matches.matches:
|
||||||
|
@ -318,16 +286,9 @@ class token:
|
||||||
# Set matchID, join stream, channel and send packet
|
# Set matchID, join stream, channel and send packet
|
||||||
self.matchID = matchID
|
self.matchID = matchID
|
||||||
self.joinStream(match.streamName)
|
self.joinStream(match.streamName)
|
||||||
chat.joinChannel(token=self, channel="#multi_{}".format(self.matchID), force=True)
|
chat.joinChannel(token=self, channel="#multi_{}".format(self.matchID))
|
||||||
self.enqueue(serverPackets.matchJoinSuccess(matchID))
|
self.enqueue(serverPackets.matchJoinSuccess(matchID))
|
||||||
|
|
||||||
if match.isTourney:
|
|
||||||
# Alert the user if we have just joined a tourney match
|
|
||||||
self.enqueue(serverPackets.notification("You are now in a tournament match."))
|
|
||||||
# If an user joins, then the ready status of the match changes and
|
|
||||||
# maybe not all users are ready.
|
|
||||||
match.sendReadyStatus()
|
|
||||||
|
|
||||||
def leaveMatch(self):
|
def leaveMatch(self):
|
||||||
"""
|
"""
|
||||||
Leave joined match, match stream and match channel
|
Leave joined match, match stream and match channel
|
||||||
|
@ -339,37 +300,28 @@ class token:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Part #multiplayer channel and streams (/ and /playing)
|
# Part #multiplayer channel and streams (/ and /playing)
|
||||||
chat.partChannel(token=self, channel="#multi_{}".format(self.matchID), kick=True, force=True)
|
chat.partChannel(token=self, channel="#multi_{}".format(self.matchID), kick=True)
|
||||||
self.leaveStream("multi/{}".format(self.matchID))
|
self.leaveStream("multi/{}".format(self.matchID))
|
||||||
self.leaveStream("multi/{}/playing".format(self.matchID)) # optional
|
self.leaveStream("multi/{}/playing".format(self.matchID)) # optional
|
||||||
|
|
||||||
# Set usertoken match to -1
|
|
||||||
leavingMatchID = self.matchID
|
|
||||||
self.matchID = -1
|
|
||||||
|
|
||||||
# Make sure the match exists
|
# Make sure the match exists
|
||||||
if leavingMatchID not in glob.matches.matches:
|
if self.matchID not in glob.matches.matches:
|
||||||
return
|
return
|
||||||
|
|
||||||
# The match exists, get object
|
# The match exists, get object
|
||||||
match = glob.matches.matches[leavingMatchID]
|
match = glob.matches.matches[self.matchID]
|
||||||
|
|
||||||
# Set slot to free
|
# Set slot to free
|
||||||
match.userLeft(self)
|
match.userLeft(self)
|
||||||
|
|
||||||
if match.isTourney:
|
# Set usertoken match to -1
|
||||||
# If an user leaves, then the ready status of the match changes and
|
self.matchID = -1
|
||||||
# maybe all users are ready. Or maybe nobody is in the match anymore
|
|
||||||
match.sendReadyStatus()
|
|
||||||
|
|
||||||
def kick(self, message="You have been kicked from the server. Please login again.", reason="kick"):
|
def kick(self, message="You have been kicked from the server. Please login again.", reason="kick"):
|
||||||
"""
|
"""
|
||||||
Kick this user from the server
|
Kick this user from the server
|
||||||
|
|
||||||
:param message: Notification message to send to this user.
|
message -- Notification message to send to this user. Optional.
|
||||||
Default: "You have been kicked from the server. Please login again."
|
|
||||||
:param reason: Kick reason, used in logs. Default: "kick"
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
# Send packet to target
|
# Send packet to target
|
||||||
log.info("{} has been disconnected. ({})".format(self.username, reason))
|
log.info("{} has been disconnected. ({})".format(self.username, reason))
|
||||||
|
@ -378,28 +330,21 @@ class token:
|
||||||
self.enqueue(serverPackets.loginFailed())
|
self.enqueue(serverPackets.loginFailed())
|
||||||
|
|
||||||
# Logout event
|
# Logout event
|
||||||
logoutEvent.handle(self, deleteToken=self.irc)
|
logoutEvent.handle(self, None)
|
||||||
|
|
||||||
def silence(self, seconds = None, reason = "", author = 999):
|
def silence(self, seconds, reason, author = 999):
|
||||||
"""
|
"""
|
||||||
Silences this user (db, packet and token)
|
Silences this user (db, packet and token)
|
||||||
|
|
||||||
:param seconds: silence length in seconds. If None, get it from db. Default: None
|
seconds -- silence length in seconds
|
||||||
:param reason: silence reason. Default: empty string
|
reason -- silence reason
|
||||||
:param author: userID of who has silenced the user. Default: 999 (FokaBot)
|
author -- userID of who has silenced the target. Optional. Default: 999 (fokabot)
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
if seconds is None:
|
# Silence in db and token
|
||||||
# Get silence expire from db if needed
|
self.silenceEndTime = int(time.time())+seconds
|
||||||
seconds = max(0, userUtils.getSilenceEnd(self.userID) - int(time.time()))
|
userUtils.silence(self.userID, seconds, reason, author)
|
||||||
else:
|
|
||||||
# Silence in db and token
|
|
||||||
userUtils.silence(self.userID, seconds, reason, author)
|
|
||||||
|
|
||||||
# Silence token
|
# Send silence packet to target
|
||||||
self.silenceEndTime = int(time.time()) + seconds
|
|
||||||
|
|
||||||
# Send silence packet to user
|
|
||||||
self.enqueue(serverPackets.silenceEndTime(seconds))
|
self.enqueue(serverPackets.silenceEndTime(seconds))
|
||||||
|
|
||||||
# Send silenced packet to everyone else
|
# Send silenced packet to everyone else
|
||||||
|
@ -409,8 +354,7 @@ class token:
|
||||||
"""
|
"""
|
||||||
Silences the user if is spamming.
|
Silences the user if is spamming.
|
||||||
|
|
||||||
:param increaseSpamRate: set to True if the user has sent a new message. Default: True
|
increaseSpamRate -- pass True if the user has sent a new message. Optional. Default: True
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
# Increase the spam rate if needed
|
# Increase the spam rate if needed
|
||||||
if increaseSpamRate:
|
if increaseSpamRate:
|
||||||
|
@ -424,7 +368,7 @@ class token:
|
||||||
"""
|
"""
|
||||||
Returns True if this user is silenced, otherwise False
|
Returns True if this user is silenced, otherwise False
|
||||||
|
|
||||||
:return: True if this user is silenced, otherwise False
|
return -- True/False
|
||||||
"""
|
"""
|
||||||
return self.silenceEndTime-int(time.time()) > 0
|
return self.silenceEndTime-int(time.time()) > 0
|
||||||
|
|
||||||
|
@ -433,16 +377,12 @@ class token:
|
||||||
Returns the seconds left for this user's silence
|
Returns the seconds left for this user's silence
|
||||||
(0 if user is not silenced)
|
(0 if user is not silenced)
|
||||||
|
|
||||||
:return: silence seconds left (or 0)
|
return -- silence seconds left
|
||||||
"""
|
"""
|
||||||
return max(0, self.silenceEndTime-int(time.time()))
|
return max(0, self.silenceEndTime-int(time.time()))
|
||||||
|
|
||||||
def updateCachedStats(self):
|
def updateCachedStats(self):
|
||||||
"""
|
"""Update all cached stats for this token"""
|
||||||
Update all cached stats for this token
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
stats = userUtils.getUserStats(self.userID, self.gameMode)
|
stats = userUtils.getUserStats(self.userID, self.gameMode)
|
||||||
log.debug(str(stats))
|
log.debug(str(stats))
|
||||||
if stats is None:
|
if stats is None:
|
||||||
|
@ -455,77 +395,37 @@ class token:
|
||||||
self.gameRank = stats["gameRank"]
|
self.gameRank = stats["gameRank"]
|
||||||
self.pp = stats["pp"]
|
self.pp = stats["pp"]
|
||||||
|
|
||||||
def checkRestricted(self):
|
def checkRestricted(self, force=False):
|
||||||
"""
|
"""
|
||||||
Check if this token is restricted. If so, send fokabot message
|
Check if this token is restricted. If so, send fokabot message
|
||||||
|
|
||||||
:return:
|
force -- If True, get restricted value from db.
|
||||||
|
If false, get the cached one. Optional. Default: False
|
||||||
"""
|
"""
|
||||||
oldRestricted = self.restricted
|
if force:
|
||||||
self.restricted = userUtils.isRestricted(self.userID)
|
self.restricted = userUtils.isRestricted(self.userID)
|
||||||
if self.restricted:
|
if self.restricted:
|
||||||
self.setRestricted()
|
self.setRestricted()
|
||||||
elif not self.restricted and oldRestricted != self.restricted:
|
|
||||||
self.resetRestricted()
|
|
||||||
|
|
||||||
def checkBanned(self):
|
|
||||||
"""
|
|
||||||
Check if this user is banned. If so, disconnect it.
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
if userUtils.isBanned(self.userID):
|
|
||||||
self.enqueue(serverPackets.loginBanned())
|
|
||||||
logoutEvent.handle(self, deleteToken=False)
|
|
||||||
|
|
||||||
|
|
||||||
def setRestricted(self):
|
def setRestricted(self):
|
||||||
"""
|
"""
|
||||||
Set this token as restricted, send FokaBot message to user
|
Set this token as restricted, send FokaBot message to user
|
||||||
and send offline packet to everyone
|
and send offline packet to everyone
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
self.restricted = True
|
self.restricted = True
|
||||||
chat.sendMessage(glob.BOT_NAME, self.username, "Your account is currently in restricted mode. Please visit ripple's website for more information.")
|
chat.sendMessage("FokaBot",self.username, "Your account is currently in restricted mode. Please visit ripple's website for more information.")
|
||||||
|
|
||||||
def resetRestricted(self):
|
|
||||||
"""
|
|
||||||
Send FokaBot message to alert the user that he has been unrestricted
|
|
||||||
and he has to log in again.
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
chat.sendMessage(glob.BOT_NAME, self.username, "Your account has been unrestricted! Please log in again.")
|
|
||||||
|
|
||||||
def joinStream(self, name):
|
def joinStream(self, name):
|
||||||
"""
|
|
||||||
Join a packet stream, or create it if the stream doesn't exist.
|
|
||||||
|
|
||||||
:param name: stream name
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
glob.streams.join(name, token=self.token)
|
glob.streams.join(name, token=self.token)
|
||||||
if name not in self.streams:
|
if name not in self.streams:
|
||||||
self.streams.append(name)
|
self.streams.append(name)
|
||||||
|
|
||||||
def leaveStream(self, name):
|
def leaveStream(self, name):
|
||||||
"""
|
|
||||||
Leave a packets stream
|
|
||||||
|
|
||||||
:param name: stream name
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
glob.streams.leave(name, token=self.token)
|
glob.streams.leave(name, token=self.token)
|
||||||
if name in self.streams:
|
if name in self.streams:
|
||||||
self.streams.remove(name)
|
self.streams.remove(name)
|
||||||
|
|
||||||
def leaveAllStreams(self):
|
def leaveAllStreams(self):
|
||||||
"""
|
|
||||||
Leave all joined packet streams
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
for i in self.streams:
|
for i in self.streams:
|
||||||
self.leaveStream(i)
|
self.leaveStream(i)
|
||||||
|
|
||||||
|
@ -542,23 +442,9 @@ class token:
|
||||||
self.sentAway.append(userID)
|
self.sentAway.append(userID)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def addMessageInBuffer(self, chan, message):
|
def updatePrivileges(self):
|
||||||
"""
|
"""
|
||||||
Add a message in messages buffer (10 messages, truncated at 50 chars).
|
Force updating self.privileges from db
|
||||||
Used as proof when the user gets reported.
|
|
||||||
|
|
||||||
:param chan: channel
|
|
||||||
:param message: message content
|
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if len(self.messagesBuffer) > 9:
|
self.privileges = userUtils.getPrivileges(self.userID)
|
||||||
self.messagesBuffer = self.messagesBuffer[1:]
|
|
||||||
self.messagesBuffer.append("{time} - {user}@{channel}: {message}".format(time=time.strftime("%H:%M", time.localtime()), user=self.username, channel=chan, message=message[:50]))
|
|
||||||
|
|
||||||
def getMessagesBufferString(self):
|
|
||||||
"""
|
|
||||||
Get the content of the messages buffer as a string
|
|
||||||
|
|
||||||
:return: messages buffer content as a string
|
|
||||||
"""
|
|
||||||
return "\n".join(x for x in self.messagesBuffer)
|
|
|
@ -43,29 +43,15 @@ class stream:
|
||||||
log.info("{} has left stream {}".format(token, self.name))
|
log.info("{} has left stream {}".format(token, self.name))
|
||||||
self.clients.remove(token)
|
self.clients.remove(token)
|
||||||
|
|
||||||
def broadcast(self, data, but=None):
|
def broadcast(self, data):
|
||||||
"""
|
"""
|
||||||
Send some data to all (or some) clients connected to this stream
|
Send some data to all clients connected to this stream
|
||||||
|
|
||||||
:param data: data to send
|
:param data: data to send
|
||||||
:param but: array of tokens to ignore. Default: None (send to everyone)
|
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if but is None:
|
|
||||||
but = []
|
|
||||||
for i in self.clients:
|
for i in self.clients:
|
||||||
if i in glob.tokens.tokens:
|
if i in glob.tokens.tokens:
|
||||||
if i not in but:
|
glob.tokens.tokens[i].enqueue(data)
|
||||||
glob.tokens.tokens[i].enqueue(data)
|
|
||||||
else:
|
else:
|
||||||
self.removeClient(token=i)
|
self.removeClient(token=i)
|
||||||
|
|
||||||
def dispose(self):
|
|
||||||
"""
|
|
||||||
Tell every client in this stream to leave the stream
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
for i in self.clients:
|
|
||||||
if i in glob.tokens.tokens:
|
|
||||||
glob.tokens.tokens[i].leaveStream(self.name)
|
|
|
@ -1,7 +1,6 @@
|
||||||
from objects import stream
|
from objects import stream
|
||||||
from objects import glob
|
from objects import glob
|
||||||
|
|
||||||
# TODO: use *args and **kwargs
|
|
||||||
class streamList:
|
class streamList:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.streams = {}
|
self.streams = {}
|
||||||
|
@ -56,39 +55,25 @@ class streamList:
|
||||||
return
|
return
|
||||||
self.streams[streamName].removeClient(client=client, token=token)
|
self.streams[streamName].removeClient(client=client, token=token)
|
||||||
|
|
||||||
def broadcast(self, streamName, data, but=None):
|
def broadcast(self, streamName, data):
|
||||||
"""
|
"""
|
||||||
Send some data to all clients in a stream
|
Send some data to all clients in a stream
|
||||||
|
|
||||||
:param streamName: stream name
|
:param streamName: stream name
|
||||||
:param data: data to send
|
:param data: data to send
|
||||||
:param but: array of tokens to ignore. Default: None (send to everyone)
|
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if streamName not in self.streams:
|
if streamName not in self.streams:
|
||||||
return
|
return
|
||||||
self.streams[streamName].broadcast(data, but)
|
self.streams[streamName].broadcast(data)
|
||||||
|
|
||||||
def dispose(self, streamName, *args, **kwargs):
|
'''def getClients(self, streamName):
|
||||||
"""
|
"""
|
||||||
Call `dispose` on `streamName`
|
Get all clients in a stream
|
||||||
|
|
||||||
:param streamName: name of the stream
|
:param streamName: name of the stream
|
||||||
:param args:
|
|
||||||
:param kwargs:
|
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if streamName not in self.streams:
|
if streamName not in self.streams:
|
||||||
return
|
return
|
||||||
self.streams[streamName].dispose(*args, **kwargs)
|
return self.streams[streamName].clients'''
|
||||||
|
|
||||||
def getStream(self, streamName):
|
|
||||||
"""
|
|
||||||
Returns streamName's stream object or None if it doesn't exist
|
|
||||||
|
|
||||||
:param streamName:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
if streamName in self.streams:
|
|
||||||
return self.streams[streamName]
|
|
||||||
return None
|
|
|
@ -1,65 +1,59 @@
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import redis
|
|
||||||
|
|
||||||
from common.ripple import userUtils
|
from common.ripple import userUtils
|
||||||
from common.log import logUtils as log
|
from common.log import logUtils as log
|
||||||
from common.sentry import sentry
|
|
||||||
from constants import serverPackets
|
from constants import serverPackets
|
||||||
from constants.exceptions import periodicLoopException
|
|
||||||
from events import logoutEvent
|
from events import logoutEvent
|
||||||
from objects import glob
|
from objects import glob
|
||||||
from objects import osuToken
|
from objects import osuToken
|
||||||
|
|
||||||
|
|
||||||
class tokenList:
|
class tokenList:
|
||||||
|
"""
|
||||||
|
List of connected osu tokens
|
||||||
|
|
||||||
|
tokens -- dictionary. key: token string, value: token object
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
"""
|
||||||
|
Initialize a tokens list
|
||||||
|
"""
|
||||||
self.tokens = {}
|
self.tokens = {}
|
||||||
self._lock = threading.Lock()
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
self._lock.acquire()
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
||||||
self._lock.release()
|
|
||||||
|
|
||||||
def addToken(self, userID, ip = "", irc = False, timeOffset=0, tournament=False):
|
def addToken(self, userID, ip = "", irc = False, timeOffset=0, tournament=False):
|
||||||
"""
|
"""
|
||||||
Add a token object to tokens list
|
Add a token object to tokens list
|
||||||
|
|
||||||
:param userID: user id associated to that token
|
userID -- user id associated to that token
|
||||||
:param ip: ip address of the client
|
irc -- if True, set this token as IRC client
|
||||||
:param irc: if True, set this token as IRC client
|
return -- token object
|
||||||
:param timeOffset: the time offset from UTC for this user. Default: 0.
|
|
||||||
:param tournament: if True, flag this client as a tournement client. Default: True.
|
|
||||||
:return: token object
|
|
||||||
"""
|
"""
|
||||||
newToken = osuToken.token(userID, ip=ip, irc=irc, timeOffset=timeOffset, tournament=tournament)
|
newToken = osuToken.token(userID, ip=ip, irc=irc, timeOffset=timeOffset, tournament=tournament)
|
||||||
self.tokens[newToken.token] = newToken
|
self.tokens[newToken.token] = newToken
|
||||||
glob.redis.incr("ripple:online_users")
|
|
||||||
return newToken
|
return newToken
|
||||||
|
|
||||||
def deleteToken(self, token):
|
def deleteToken(self, token):
|
||||||
"""
|
"""
|
||||||
Delete a token from token list if it exists
|
Delete a token from token list if it exists
|
||||||
|
|
||||||
:param token: token string
|
token -- token string
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
if token in self.tokens:
|
if token in self.tokens:
|
||||||
|
# Delete session from DB
|
||||||
if self.tokens[token].ip != "":
|
if self.tokens[token].ip != "":
|
||||||
userUtils.deleteBanchoSessions(self.tokens[token].userID, self.tokens[token].ip)
|
userUtils.deleteBanchoSessions(self.tokens[token].userID, self.tokens[token].ip)
|
||||||
t = self.tokens.pop(token)
|
|
||||||
del t
|
# Pop token from list
|
||||||
glob.redis.decr("ripple:online_users")
|
self.tokens.pop(token)
|
||||||
|
|
||||||
def getUserIDFromToken(self, token):
|
def getUserIDFromToken(self, token):
|
||||||
"""
|
"""
|
||||||
Get user ID from a token
|
Get user ID from a token
|
||||||
|
|
||||||
:param token: token to find
|
token -- token to find
|
||||||
:return: False if not found, userID if found
|
return -- false if not found, userID if found
|
||||||
"""
|
"""
|
||||||
# Make sure the token exists
|
# Make sure the token exists
|
||||||
if token not in self.tokens:
|
if token not in self.tokens:
|
||||||
|
@ -68,91 +62,63 @@ class tokenList:
|
||||||
# Get userID associated to that token
|
# Get userID associated to that token
|
||||||
return self.tokens[token].userID
|
return self.tokens[token].userID
|
||||||
|
|
||||||
def getTokenFromUserID(self, userID, ignoreIRC=False, _all=False):
|
def getTokenFromUserID(self, userID, ignoreIRC=False):
|
||||||
"""
|
"""
|
||||||
Get token from a user ID
|
Get token from a user ID
|
||||||
|
|
||||||
:param userID: user ID to find
|
userID -- user ID to find
|
||||||
:param ignoreIRC: if True, consider bancho clients only and skip IRC clients
|
return -- False if not found, token object if found
|
||||||
:param _all: if True, return a list with all clients that match given username, otherwise return
|
|
||||||
only the first occurrence.
|
|
||||||
:return: False if not found, token object if found
|
|
||||||
"""
|
"""
|
||||||
# Make sure the token exists
|
# Make sure the token exists
|
||||||
ret = []
|
|
||||||
for _, value in self.tokens.items():
|
for _, value in self.tokens.items():
|
||||||
if value.userID == userID:
|
if value.userID == userID:
|
||||||
if ignoreIRC and value.irc:
|
if ignoreIRC and value.irc:
|
||||||
continue
|
continue
|
||||||
if _all:
|
return value
|
||||||
ret.append(value)
|
|
||||||
else:
|
|
||||||
return value
|
|
||||||
|
|
||||||
# Return full list or None if not found
|
# Return none if not found
|
||||||
if _all:
|
return None
|
||||||
return ret
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def getTokenFromUsername(self, username, ignoreIRC=False, safe=False, _all=False):
|
def getTokenFromUsername(self, username, ignoreIRC=False):
|
||||||
"""
|
"""
|
||||||
Get an osuToken object from an username
|
Get token from a username
|
||||||
|
|
||||||
:param username: normal username or safe username
|
username -- username to find
|
||||||
:param ignoreIRC: if True, consider bancho clients only and skip IRC clients
|
return -- False if not found, token object if found
|
||||||
:param safe: if True, username is a safe username,
|
|
||||||
compare it with token's safe username rather than normal username
|
|
||||||
:param _all: if True, return a list with all clients that match given username, otherwise return
|
|
||||||
only the first occurrence.
|
|
||||||
:return: osuToken object or None
|
|
||||||
"""
|
"""
|
||||||
# lowercase
|
# lowercase
|
||||||
who = username.lower() if not safe else username
|
who = username.lower()
|
||||||
|
|
||||||
# Make sure the token exists
|
# Make sure the token exists
|
||||||
ret = []
|
|
||||||
for _, value in self.tokens.items():
|
for _, value in self.tokens.items():
|
||||||
if (not safe and value.username.lower() == who) or (safe and value.safeUsername == who):
|
if value.username.lower() == who:
|
||||||
if ignoreIRC and value.irc:
|
if ignoreIRC and value.irc:
|
||||||
continue
|
continue
|
||||||
if _all:
|
return value
|
||||||
ret.append(value)
|
|
||||||
else:
|
|
||||||
return value
|
|
||||||
|
|
||||||
# Return full list or None if not found
|
# Return none if not found
|
||||||
if _all:
|
return None
|
||||||
return ret
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def deleteOldTokens(self, userID):
|
def deleteOldTokens(self, userID):
|
||||||
"""
|
"""
|
||||||
Delete old userID's tokens if found
|
Delete old userID's tokens if found
|
||||||
|
|
||||||
:param userID: tokens associated to this user will be deleted
|
userID -- tokens associated to this user will be deleted
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Delete older tokens
|
# Delete older tokens
|
||||||
delete = []
|
|
||||||
for key, value in list(self.tokens.items()):
|
for key, value in list(self.tokens.items()):
|
||||||
if value.userID == userID:
|
if value.userID == userID:
|
||||||
# Delete this token from the dictionary
|
# Delete this token from the dictionary
|
||||||
#self.tokens[key].kick("You have logged in from somewhere else. You can't connect to Bancho/IRC from more than one device at the same time.", "kicked, multiple clients")
|
self.tokens[key].kick("You have logged in from somewhere else. You can't connect to Bancho/IRC from more than one device at the same time.", "kicked, multiple clients")
|
||||||
delete.append(self.tokens[key])
|
|
||||||
|
|
||||||
for i in delete:
|
|
||||||
logoutEvent.handle(i)
|
|
||||||
|
|
||||||
def multipleEnqueue(self, packet, who, but = False):
|
def multipleEnqueue(self, packet, who, but = False):
|
||||||
"""
|
"""
|
||||||
Enqueue a packet to multiple users
|
Enqueue a packet to multiple users
|
||||||
|
|
||||||
:param packet: packet bytes to enqueue
|
packet -- packet bytes to enqueue
|
||||||
:param who: userIDs array
|
who -- userIDs array
|
||||||
:param but: if True, enqueue to everyone but users in `who` array
|
but -- if True, enqueue to everyone but users in who array
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
for _, value in self.tokens.items():
|
for _, value in self.tokens.items():
|
||||||
shouldEnqueue = False
|
shouldEnqueue = False
|
||||||
|
@ -168,93 +134,68 @@ class tokenList:
|
||||||
"""
|
"""
|
||||||
Enqueue packet(s) to every connected user
|
Enqueue packet(s) to every connected user
|
||||||
|
|
||||||
:param packet: packet bytes to enqueue
|
packet -- packet bytes to enqueue
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
for _, value in self.tokens.items():
|
for _, value in self.tokens.items():
|
||||||
value.enqueue(packet)
|
value.enqueue(packet)
|
||||||
|
|
||||||
@sentry.capture()
|
def usersTimeoutCheckLoop(self, timeoutTime = 100, checkTime = 100):
|
||||||
def usersTimeoutCheckLoop(self):
|
|
||||||
"""
|
"""
|
||||||
Start timed out users disconnect loop.
|
Deletes all timed out users.
|
||||||
This function will be called every `checkTime` seconds and so on, forever.
|
If called once, will recall after checkTime seconds and so on, forever
|
||||||
CALL THIS FUNCTION ONLY ONCE!
|
CALL THIS FUNCTION ONLY ONCE!
|
||||||
:return:
|
|
||||||
|
timeoutTime - seconds of inactivity required to disconnect someone (Default: 100)
|
||||||
|
checkTime - seconds between loops (Default: 100)
|
||||||
"""
|
"""
|
||||||
try:
|
log.debug("Checking timed out clients")
|
||||||
log.debug("Checking timed out clients")
|
timedOutTokens = [] # timed out users
|
||||||
exceptions = []
|
timeoutLimit = int(time.time())-timeoutTime
|
||||||
timedOutTokens = [] # timed out users
|
for key, value in self.tokens.items():
|
||||||
timeoutLimit = int(time.time()) - 100
|
# Check timeout (fokabot is ignored)
|
||||||
for key, value in self.tokens.items():
|
if value.pingTime < timeoutLimit and value.userID != 999 and value.irc == False and value.tournament == False:
|
||||||
# Check timeout (fokabot is ignored)
|
# That user has timed out, add to disconnected tokens
|
||||||
if value.pingTime < timeoutLimit and value.userID != 999 and not value.irc and not value.tournament:
|
# We can't delete it while iterating or items() throws an error
|
||||||
# That user has timed out, add to disconnected tokens
|
timedOutTokens.append(key)
|
||||||
# We can't delete it while iterating or items() throws an error
|
|
||||||
timedOutTokens.append(key)
|
|
||||||
|
|
||||||
# Delete timed out users from self.tokens
|
# Delete timed out users from self.tokens
|
||||||
# i is token string (dictionary key)
|
# i is token string (dictionary key)
|
||||||
for i in timedOutTokens:
|
for i in timedOutTokens:
|
||||||
log.debug("{} timed out!!".format(self.tokens[i].username))
|
log.debug("{} timed out!!".format(self.tokens[i].username))
|
||||||
self.tokens[i].enqueue(serverPackets.notification("Your connection to the server timed out."))
|
self.tokens[i].enqueue(serverPackets.notification("Your connection to the server timed out."))
|
||||||
try:
|
logoutEvent.handle(self.tokens[i], None)
|
||||||
logoutEvent.handle(self.tokens[i], None)
|
|
||||||
except Exception as e:
|
|
||||||
exceptions.append(e)
|
|
||||||
log.error(
|
|
||||||
"Something wrong happened while disconnecting a timed out client. Reporting to Sentry "
|
|
||||||
"when the loop ends."
|
|
||||||
)
|
|
||||||
del timedOutTokens
|
|
||||||
|
|
||||||
# Re-raise exceptions if needed
|
# Schedule a new check (endless loop)
|
||||||
if exceptions:
|
threading.Timer(checkTime, self.usersTimeoutCheckLoop, [timeoutTime, checkTime]).start()
|
||||||
raise periodicLoopException(exceptions)
|
|
||||||
finally:
|
|
||||||
# Schedule a new check (endless loop)
|
|
||||||
threading.Timer(100, self.usersTimeoutCheckLoop).start()
|
|
||||||
|
|
||||||
@sentry.capture()
|
|
||||||
def spamProtectionResetLoop(self):
|
def spamProtectionResetLoop(self):
|
||||||
"""
|
"""
|
||||||
Start spam protection reset loop.
|
Reset spam rate every 10 seconds.
|
||||||
Called every 10 seconds.
|
|
||||||
CALL THIS FUNCTION ONLY ONCE!
|
CALL THIS FUNCTION ONLY ONCE!
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
try:
|
# Reset spamRate for every token
|
||||||
# Reset spamRate for every token
|
for _, value in self.tokens.items():
|
||||||
for _, value in self.tokens.items():
|
value.spamRate = 0
|
||||||
value.spamRate = 0
|
|
||||||
finally:
|
# Schedule a new check (endless loop)
|
||||||
# Schedule a new check (endless loop)
|
threading.Timer(10, self.spamProtectionResetLoop).start()
|
||||||
threading.Timer(10, self.spamProtectionResetLoop).start()
|
|
||||||
|
|
||||||
def deleteBanchoSessions(self):
|
def deleteBanchoSessions(self):
|
||||||
"""
|
"""
|
||||||
Remove all `peppy:sessions:*` redis keys.
|
Truncate bancho_sessions table.
|
||||||
Call at bancho startup to delete old cached sessions
|
Call at bancho startup to delete old cached sessions
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
try:
|
glob.db.execute("TRUNCATE TABLE bancho_sessions")
|
||||||
# TODO: Make function or some redis meme
|
|
||||||
glob.redis.eval("return redis.call('del', unpack(redis.call('keys', ARGV[1])))", 0, "peppy:sessions:*")
|
|
||||||
except redis.RedisError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def tokenExists(self, username = "", userID = -1):
|
def tokenExists(self, username = "", userID = -1):
|
||||||
"""
|
"""
|
||||||
Check if a token exists
|
Check if a token exists (aka check if someone is connected)
|
||||||
Use username or userid, not both at the same time.
|
|
||||||
|
|
||||||
:param username: Optional.
|
username -- Optional.
|
||||||
:param userID: Optional.
|
userID -- Optional.
|
||||||
:return: True if it exists, otherwise False
|
return -- True if it exists, otherwise False
|
||||||
|
|
||||||
|
Use username or userid, not both at the same time.
|
||||||
"""
|
"""
|
||||||
if userID > -1:
|
if userID > -1:
|
||||||
return True if self.getTokenFromUserID(userID) is not None else False
|
return True if self.getTokenFromUserID(userID) is not None else False
|
||||||
|
|
146
pep.py
146
pep.py
|
@ -1,24 +1,21 @@
|
||||||
|
"""Hello, pep.py here, ex-owner of ripple and prime minister of Ripwot."""
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
from multiprocessing.pool import ThreadPool
|
from multiprocessing.pool import ThreadPool
|
||||||
|
|
||||||
import tornado.gen
|
import tornado.gen
|
||||||
import tornado.httpserver
|
import tornado.httpserver
|
||||||
import tornado.ioloop
|
import tornado.ioloop
|
||||||
import tornado.web
|
import tornado.web
|
||||||
from raven.contrib.tornado import AsyncSentryClient
|
from raven.contrib.tornado import AsyncSentryClient
|
||||||
import redis
|
|
||||||
|
|
||||||
import json
|
from common import generalUtils
|
||||||
import shutil
|
|
||||||
from distutils.version import LooseVersion
|
|
||||||
|
|
||||||
from common import generalUtils, agpl
|
|
||||||
from common.constants import bcolors
|
from common.constants import bcolors
|
||||||
from common.db import dbConnector
|
from common.db import dbConnector
|
||||||
from common.ddog import datadogClient
|
from common.ddog import datadogClient
|
||||||
from common.log import logUtils as log
|
from common.log import logUtils as log
|
||||||
from common.redis import pubSub
|
from common.ripple import userUtils
|
||||||
from common.web import schiavo
|
from common.web import schiavo
|
||||||
from handlers import apiFokabotMessageHandler
|
from handlers import apiFokabotMessageHandler
|
||||||
from handlers import apiIsOnlineHandler
|
from handlers import apiIsOnlineHandler
|
||||||
|
@ -36,13 +33,6 @@ from objects import banchoConfig
|
||||||
from objects import chatFilters
|
from objects import chatFilters
|
||||||
from objects import fokabot
|
from objects import fokabot
|
||||||
from objects import glob
|
from objects import glob
|
||||||
from pubSubHandlers import changeUsernameHandler
|
|
||||||
|
|
||||||
from pubSubHandlers import disconnectHandler
|
|
||||||
from pubSubHandlers import banHandler
|
|
||||||
from pubSubHandlers import notificationHandler
|
|
||||||
from pubSubHandlers import updateSilenceHandler
|
|
||||||
from pubSubHandlers import updateStatsHandler
|
|
||||||
|
|
||||||
|
|
||||||
def make_app():
|
def make_app():
|
||||||
|
@ -57,15 +47,7 @@ def make_app():
|
||||||
(r"/stress", heavyHandler.handler)
|
(r"/stress", heavyHandler.handler)
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# AGPL license agreement
|
|
||||||
try:
|
|
||||||
agpl.check_license("ripple", "pep.py")
|
|
||||||
except agpl.LicenseError as e:
|
|
||||||
print(str(e))
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Server start
|
# Server start
|
||||||
consoleHelper.printServerStartHeader(True)
|
consoleHelper.printServerStartHeader(True)
|
||||||
|
@ -90,34 +72,6 @@ if __name__ == "__main__":
|
||||||
else:
|
else:
|
||||||
consoleHelper.printDone()
|
consoleHelper.printDone()
|
||||||
|
|
||||||
# Read additional config file
|
|
||||||
consoleHelper.printNoNl("> Loading additional config file... ")
|
|
||||||
try:
|
|
||||||
if not os.path.isfile(glob.conf.config["custom"]["config"]):
|
|
||||||
consoleHelper.printWarning()
|
|
||||||
consoleHelper.printColored("[!] Missing config file at {}; A default one has been generated at this location.".format(glob.conf.config["custom"]["config"]), bcolors.YELLOW)
|
|
||||||
shutil.copy("common/default_config.json", glob.conf.config["custom"]["config"])
|
|
||||||
|
|
||||||
with open(glob.conf.config["custom"]["config"], "r") as f:
|
|
||||||
glob.conf.extra = json.load(f)
|
|
||||||
|
|
||||||
consoleHelper.printDone()
|
|
||||||
except:
|
|
||||||
consoleHelper.printWarning()
|
|
||||||
consoleHelper.printColored("[!] Unable to load custom config at {}".format(glob.conf.config["custom"]["config"]), bcolors.RED)
|
|
||||||
consoleHelper.printColored("[!] Make sure you have the latest osufx common submodule!", bcolors.RED)
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
# Check if running common module is usable
|
|
||||||
if glob.COMMON_VERSION == "Unknown":
|
|
||||||
consoleHelper.printWarning()
|
|
||||||
consoleHelper.printColored("[!] You do not seem to be using osufx's common submodule... nothing will work...", bcolors.RED)
|
|
||||||
consoleHelper.printColored("[!] You can download or fork the submodule from {}https://github.com/osufx/ripple-python-common".format(bcolors.UNDERLINE), bcolors.RED)
|
|
||||||
sys.exit()
|
|
||||||
elif LooseVersion(glob.COMMON_VERSION_REQ) > LooseVersion(glob.COMMON_VERSION):
|
|
||||||
consoleHelper.printColored("[!] Your common submodule version is below the required version number for this version of pep.py.", bcolors.RED)
|
|
||||||
consoleHelper.printColored("[!] You are highly adviced to update your common submodule as stability may vary with outdated modules.", bcolors.RED)
|
|
||||||
|
|
||||||
# Create data folder if needed
|
# Create data folder if needed
|
||||||
consoleHelper.printNoNl("> Checking folders... ")
|
consoleHelper.printNoNl("> Checking folders... ")
|
||||||
paths = [".data"]
|
paths = [".data"]
|
||||||
|
@ -128,7 +82,7 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
# Connect to db
|
# Connect to db
|
||||||
try:
|
try:
|
||||||
consoleHelper.printNoNl("> Connecting to MySQL database... ")
|
consoleHelper.printNoNl("> Connecting to MySQL database...")
|
||||||
glob.db = dbConnector.db(glob.conf.config["db"]["host"], glob.conf.config["db"]["username"], glob.conf.config["db"]["password"], glob.conf.config["db"]["database"], int(glob.conf.config["db"]["workers"]))
|
glob.db = dbConnector.db(glob.conf.config["db"]["host"], glob.conf.config["db"]["username"], glob.conf.config["db"]["password"], glob.conf.config["db"]["database"], int(glob.conf.config["db"]["workers"]))
|
||||||
consoleHelper.printNoNl(" ")
|
consoleHelper.printNoNl(" ")
|
||||||
consoleHelper.printDone()
|
consoleHelper.printDone()
|
||||||
|
@ -138,31 +92,6 @@ if __name__ == "__main__":
|
||||||
consoleHelper.printColored("[!] Error while connection to database. Please check your config.ini and run the server again", bcolors.RED)
|
consoleHelper.printColored("[!] Error while connection to database. Please check your config.ini and run the server again", bcolors.RED)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# Connect to redis
|
|
||||||
try:
|
|
||||||
consoleHelper.printNoNl("> Connecting to redis... ")
|
|
||||||
glob.redis = redis.Redis(glob.conf.config["redis"]["host"], glob.conf.config["redis"]["port"], glob.conf.config["redis"]["database"], glob.conf.config["redis"]["password"])
|
|
||||||
glob.redis.ping()
|
|
||||||
consoleHelper.printNoNl(" ")
|
|
||||||
consoleHelper.printDone()
|
|
||||||
except:
|
|
||||||
# Exception while connecting to db
|
|
||||||
consoleHelper.printError()
|
|
||||||
consoleHelper.printColored("[!] Error while connection to redis. Please check your config.ini and run the server again", bcolors.RED)
|
|
||||||
raise
|
|
||||||
|
|
||||||
# Empty redis cache
|
|
||||||
try:
|
|
||||||
# TODO: Make function or some redis meme
|
|
||||||
glob.redis.set("ripple:online_users", 0)
|
|
||||||
glob.redis.eval("return redis.call('del', unpack(redis.call('keys', ARGV[1])))", 0, "peppy:*")
|
|
||||||
except redis.exceptions.ResponseError:
|
|
||||||
# Script returns error if there are no keys starting with peppy:*
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Save peppy version in redis
|
|
||||||
glob.redis.set("peppy:version", glob.VERSION)
|
|
||||||
|
|
||||||
# Load bancho_settings
|
# Load bancho_settings
|
||||||
try:
|
try:
|
||||||
consoleHelper.printNoNl("> Loading bancho settings from DB... ")
|
consoleHelper.printNoNl("> Loading bancho settings from DB... ")
|
||||||
|
@ -183,7 +112,7 @@ if __name__ == "__main__":
|
||||||
consoleHelper.printNoNl("> Creating threads pool... ")
|
consoleHelper.printNoNl("> Creating threads pool... ")
|
||||||
glob.pool = ThreadPool(int(glob.conf.config["server"]["threads"]))
|
glob.pool = ThreadPool(int(glob.conf.config["server"]["threads"]))
|
||||||
consoleHelper.printDone()
|
consoleHelper.printDone()
|
||||||
except ValueError:
|
except:
|
||||||
consoleHelper.printError()
|
consoleHelper.printError()
|
||||||
consoleHelper.printColored("[!] Error while creating threads pool. Please check your config.ini and run the server again", bcolors.RED)
|
consoleHelper.printColored("[!] Error while creating threads pool. Please check your config.ini and run the server again", bcolors.RED)
|
||||||
|
|
||||||
|
@ -196,11 +125,6 @@ if __name__ == "__main__":
|
||||||
consoleHelper.printColored("[!] Error while loading chat filters. Make sure there is a filters.txt file present", bcolors.RED)
|
consoleHelper.printColored("[!] Error while loading chat filters. Make sure there is a filters.txt file present", bcolors.RED)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# Start fokabot
|
|
||||||
consoleHelper.printNoNl("> Connecting bot... ")
|
|
||||||
fokabot.connect()
|
|
||||||
consoleHelper.printDone()
|
|
||||||
|
|
||||||
# Initialize chat channels
|
# Initialize chat channels
|
||||||
print("> Initializing chat channels... ")
|
print("> Initializing chat channels... ")
|
||||||
glob.channels.loadChannels()
|
glob.channels.loadChannels()
|
||||||
|
@ -212,6 +136,11 @@ if __name__ == "__main__":
|
||||||
glob.streams.add("lobby")
|
glob.streams.add("lobby")
|
||||||
consoleHelper.printDone()
|
consoleHelper.printDone()
|
||||||
|
|
||||||
|
# Start fokabot
|
||||||
|
consoleHelper.printNoNl("> Connecting FokaBot... ")
|
||||||
|
fokabot.connect()
|
||||||
|
consoleHelper.printDone()
|
||||||
|
|
||||||
# Initialize user timeout check loop
|
# Initialize user timeout check loop
|
||||||
consoleHelper.printNoNl("> Initializing user timeout check loop... ")
|
consoleHelper.printNoNl("> Initializing user timeout check loop... ")
|
||||||
glob.tokens.usersTimeoutCheckLoop()
|
glob.tokens.usersTimeoutCheckLoop()
|
||||||
|
@ -222,9 +151,9 @@ if __name__ == "__main__":
|
||||||
glob.tokens.spamProtectionResetLoop()
|
glob.tokens.spamProtectionResetLoop()
|
||||||
consoleHelper.printDone()
|
consoleHelper.printDone()
|
||||||
|
|
||||||
# Initialize multiplayer cleanup loop
|
# Cache user ids
|
||||||
consoleHelper.printNoNl("> Initializing multiplayer cleanup loop... ")
|
consoleHelper.printNoNl("> Caching user IDs... ")
|
||||||
glob.matches.cleanupLoop()
|
userUtils.cacheUserIDs()
|
||||||
consoleHelper.printDone()
|
consoleHelper.printDone()
|
||||||
|
|
||||||
# Localize warning
|
# Localize warning
|
||||||
|
@ -234,7 +163,7 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
# Discord
|
# Discord
|
||||||
if generalUtils.stringToBool(glob.conf.config["discord"]["enable"]):
|
if generalUtils.stringToBool(glob.conf.config["discord"]["enable"]):
|
||||||
glob.schiavo = schiavo.schiavo(glob.conf.config["discord"]["boturl"], "**pep.py**")
|
glob.schiavo = schiavo.schiavo(glob.conf.config["discord"]["boturl"])
|
||||||
else:
|
else:
|
||||||
consoleHelper.printColored("[!] Warning! Discord logging is disabled!", bcolors.YELLOW)
|
consoleHelper.printColored("[!] Warning! Discord logging is disabled!", bcolors.YELLOW)
|
||||||
|
|
||||||
|
@ -258,7 +187,7 @@ if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
glob.sentry = generalUtils.stringToBool(glob.conf.config["sentry"]["enable"])
|
glob.sentry = generalUtils.stringToBool(glob.conf.config["sentry"]["enable"])
|
||||||
if glob.sentry:
|
if glob.sentry:
|
||||||
glob.application.sentry_client = AsyncSentryClient(glob.conf.config["sentry"]["banchodsn"], release=glob.VERSION)
|
glob.application.sentry_client = AsyncSentryClient(glob.conf.config["sentry"]["banchodns"], release=glob.VERSION)
|
||||||
else:
|
else:
|
||||||
consoleHelper.printColored("[!] Warning! Sentry logging is disabled!", bcolors.YELLOW)
|
consoleHelper.printColored("[!] Warning! Sentry logging is disabled!", bcolors.YELLOW)
|
||||||
except:
|
except:
|
||||||
|
@ -274,16 +203,18 @@ if __name__ == "__main__":
|
||||||
datadogClient.periodicCheck("online_users", lambda: len(glob.tokens.tokens)),
|
datadogClient.periodicCheck("online_users", lambda: len(glob.tokens.tokens)),
|
||||||
datadogClient.periodicCheck("multiplayer_matches", lambda: len(glob.matches.matches)),
|
datadogClient.periodicCheck("multiplayer_matches", lambda: len(glob.matches.matches)),
|
||||||
|
|
||||||
#datadogClient.periodicCheck("ram_clients", lambda: generalUtils.getTotalSize(glob.tokens)),
|
datadogClient.periodicCheck("ram_clients", lambda: generalUtils.getTotalSize(glob.tokens)),
|
||||||
#datadogClient.periodicCheck("ram_matches", lambda: generalUtils.getTotalSize(glob.matches)),
|
datadogClient.periodicCheck("ram_matches", lambda: generalUtils.getTotalSize(glob.matches)),
|
||||||
#datadogClient.periodicCheck("ram_channels", lambda: generalUtils.getTotalSize(glob.channels)),
|
datadogClient.periodicCheck("ram_channels", lambda: generalUtils.getTotalSize(glob.channels)),
|
||||||
#datadogClient.periodicCheck("ram_file_buffers", lambda: generalUtils.getTotalSize(glob.fileBuffers)),
|
datadogClient.periodicCheck("ram_file_buffers", lambda: generalUtils.getTotalSize(glob.fileBuffers)),
|
||||||
#datadogClient.periodicCheck("ram_file_locks", lambda: generalUtils.getTotalSize(glob.fLocks)),
|
datadogClient.periodicCheck("ram_file_locks", lambda: generalUtils.getTotalSize(glob.fLocks)),
|
||||||
#datadogClient.periodicCheck("ram_datadog", lambda: generalUtils.getTotalSize(glob.datadogClient)),
|
datadogClient.periodicCheck("ram_datadog", lambda: generalUtils.getTotalSize(glob.datadogClient)),
|
||||||
#datadogClient.periodicCheck("ram_verified_cache", lambda: generalUtils.getTotalSize(glob.verifiedCache)),
|
datadogClient.periodicCheck("ram_verified_cache", lambda: generalUtils.getTotalSize(glob.verifiedCache)),
|
||||||
#datadogClient.periodicCheck("ram_irc", lambda: generalUtils.getTotalSize(glob.ircServer)),
|
datadogClient.periodicCheck("ram_userid_cache", lambda: generalUtils.getTotalSize(glob.userIDCache)),
|
||||||
#datadogClient.periodicCheck("ram_tornado", lambda: generalUtils.getTotalSize(glob.application)),
|
#datadogClient.periodicCheck("ram_pool", lambda: generalUtils.getTotalSize(glob.pool)),
|
||||||
#datadogClient.periodicCheck("ram_db", lambda: generalUtils.getTotalSize(glob.db)),
|
datadogClient.periodicCheck("ram_irc", lambda: generalUtils.getTotalSize(glob.ircServer)),
|
||||||
|
datadogClient.periodicCheck("ram_tornado", lambda: generalUtils.getTotalSize(glob.application)),
|
||||||
|
datadogClient.periodicCheck("ram_db", lambda: generalUtils.getTotalSize(glob.db)),
|
||||||
])
|
])
|
||||||
else:
|
else:
|
||||||
consoleHelper.printColored("[!] Warning! Datadog stats tracking is disabled!", bcolors.YELLOW)
|
consoleHelper.printColored("[!] Warning! Datadog stats tracking is disabled!", bcolors.YELLOW)
|
||||||
|
@ -294,39 +225,26 @@ if __name__ == "__main__":
|
||||||
glob.irc = generalUtils.stringToBool(glob.conf.config["irc"]["enable"])
|
glob.irc = generalUtils.stringToBool(glob.conf.config["irc"]["enable"])
|
||||||
if glob.irc:
|
if glob.irc:
|
||||||
# IRC port
|
# IRC port
|
||||||
ircPort = 0
|
|
||||||
try:
|
try:
|
||||||
ircPort = int(glob.conf.config["irc"]["port"])
|
ircPort = int(glob.conf.config["irc"]["port"])
|
||||||
except ValueError:
|
except:
|
||||||
consoleHelper.printColored("[!] Invalid IRC port! Please check your config.ini and run the server again", bcolors.RED)
|
consoleHelper.printColored("[!] Invalid IRC port! Please check your config.ini and run the server again", bcolors.RED)
|
||||||
log.logMessage("IRC server started!", discord="bunker", of="info.txt", stdout=False)
|
log.logMessage("**pep.py** IRC server started!", discord="bunker", of="info.txt", stdout=False)
|
||||||
consoleHelper.printColored("> IRC server listening on 127.0.0.1:{}...".format(ircPort), bcolors.GREEN)
|
consoleHelper.printColored("> IRC server listening on 127.0.0.1:{}...".format(ircPort), bcolors.GREEN)
|
||||||
threading.Thread(target=lambda: ircserver.main(port=ircPort)).start()
|
threading.Thread(target=lambda: ircserver.main(port=ircPort)).start()
|
||||||
else:
|
else:
|
||||||
consoleHelper.printColored("[!] Warning! IRC server is disabled!", bcolors.YELLOW)
|
consoleHelper.printColored("[!] Warning! IRC server is disabled!", bcolors.YELLOW)
|
||||||
|
|
||||||
# Server port
|
# Server port
|
||||||
serverPort = 0
|
|
||||||
try:
|
try:
|
||||||
serverPort = int(glob.conf.config["server"]["port"])
|
serverPort = int(glob.conf.config["server"]["port"])
|
||||||
except ValueError:
|
except:
|
||||||
consoleHelper.printColored("[!] Invalid server port! Please check your config.ini and run the server again", bcolors.RED)
|
consoleHelper.printColored("[!] Invalid server port! Please check your config.ini and run the server again", bcolors.RED)
|
||||||
|
|
||||||
# Server start message and console output
|
# Server start message and console output
|
||||||
log.logMessage("Server started!", discord="bunker", of="info.txt", stdout=False)
|
log.logMessage("**pep.py** Server started!", discord="bunker", of="info.txt", stdout=False)
|
||||||
consoleHelper.printColored("> Tornado listening for HTTP(s) clients on 127.0.0.1:{}...".format(serverPort), bcolors.GREEN)
|
consoleHelper.printColored("> Tornado listening for HTTP(s) clients on 127.0.0.1:{}...".format(serverPort), bcolors.GREEN)
|
||||||
|
|
||||||
# Connect to pubsub channels
|
|
||||||
pubSub.listener(glob.redis, {
|
|
||||||
"peppy:disconnect": disconnectHandler.handler(),
|
|
||||||
"peppy:change_username": changeUsernameHandler.handler(),
|
|
||||||
"peppy:reload_settings": lambda x: x == b"reload" and glob.banchoConf.reload(),
|
|
||||||
"peppy:update_cached_stats": updateStatsHandler.handler(),
|
|
||||||
"peppy:silence": updateSilenceHandler.handler(),
|
|
||||||
"peppy:ban": banHandler.handler(),
|
|
||||||
"peppy:notification": notificationHandler.handler(),
|
|
||||||
}).start()
|
|
||||||
|
|
||||||
# Start tornado
|
# Start tornado
|
||||||
glob.application.listen(serverPort)
|
glob.application.listen(serverPort)
|
||||||
tornado.ioloop.IOLoop.instance().start()
|
tornado.ioloop.IOLoop.instance().start()
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
from common.redis import generalPubSubHandler
|
|
||||||
from common.ripple import userUtils
|
|
||||||
from objects import glob
|
|
||||||
|
|
||||||
class handler(generalPubSubHandler.generalPubSubHandler):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.type = "int"
|
|
||||||
|
|
||||||
def handle(self, userID):
|
|
||||||
userID = super().parseData(userID)
|
|
||||||
if userID is None:
|
|
||||||
return
|
|
||||||
targetToken = glob.tokens.getTokenFromUserID(userID)
|
|
||||||
if targetToken is not None:
|
|
||||||
targetToken.privileges = userUtils.getPrivileges(userID)
|
|
||||||
targetToken.checkBanned()
|
|
||||||
targetToken.checkRestricted()
|
|
|
@ -1,50 +0,0 @@
|
||||||
from common.redis import generalPubSubHandler
|
|
||||||
from common.ripple import userUtils
|
|
||||||
from common.log import logUtils as log
|
|
||||||
from common.constants import actions
|
|
||||||
from objects import glob
|
|
||||||
|
|
||||||
def handleUsernameChange(userID, newUsername, targetToken=None):
|
|
||||||
try:
|
|
||||||
userUtils.appendNotes(userID, "Username change: '{}' -> '{}'".format(userUtils.getUsername(userID), newUsername))
|
|
||||||
userUtils.changeUsername(userID, newUsername=newUsername)
|
|
||||||
if targetToken is not None:
|
|
||||||
targetToken.kick("Your username has been changed to {}. Please log in again.".format(newUsername), "username_change")
|
|
||||||
except userUtils.usernameAlreadyInUseError:
|
|
||||||
log.rap(999, "Username change: {} is already in use!", through="Bancho")
|
|
||||||
if targetToken is not None:
|
|
||||||
targetToken.kick("There was a critical error while trying to change your username. Please contact a developer.", "username_change_fail")
|
|
||||||
except userUtils.invalidUsernameError:
|
|
||||||
log.rap(999, "Username change: {} is not a valid username!", through="Bancho")
|
|
||||||
if targetToken is not None:
|
|
||||||
targetToken.kick("There was a critical error while trying to change your username. Please contact a developer.", "username_change_fail")
|
|
||||||
|
|
||||||
class handler(generalPubSubHandler.generalPubSubHandler):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.structure = {
|
|
||||||
"userID": 0,
|
|
||||||
"newUsername": ""
|
|
||||||
}
|
|
||||||
|
|
||||||
def handle(self, data):
|
|
||||||
data = super().parseData(data)
|
|
||||||
if data is None:
|
|
||||||
return
|
|
||||||
# Get the user's token
|
|
||||||
targetToken = glob.tokens.getTokenFromUserID(data["userID"])
|
|
||||||
if targetToken is None:
|
|
||||||
# If the user is offline change username immediately
|
|
||||||
handleUsernameChange(data["userID"], data["newUsername"])
|
|
||||||
else:
|
|
||||||
if targetToken.irc or (targetToken.actionID != actions.PLAYING and targetToken.actionID != actions.MULTIPLAYING):
|
|
||||||
# If the user is online and he's connected through IRC or he's not playing,
|
|
||||||
# change username and kick the user immediately
|
|
||||||
handleUsernameChange(data["userID"], data["newUsername"], targetToken)
|
|
||||||
else:
|
|
||||||
# If the user is playing, delay the username change until he submits the score
|
|
||||||
# On submit modular, lets will send the username change request again
|
|
||||||
# through redis once the score has been submitted
|
|
||||||
# The check is performed on bancho logout too, so if the user disconnects
|
|
||||||
# without submitting a score, the username gets changed on bancho logout
|
|
||||||
glob.redis.set("ripple:change_username_pending:{}".format(data["userID"]), data["newUsername"])
|
|
|
@ -1,18 +0,0 @@
|
||||||
from common.redis import generalPubSubHandler
|
|
||||||
from objects import glob
|
|
||||||
|
|
||||||
class handler(generalPubSubHandler.generalPubSubHandler):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.structure = {
|
|
||||||
"userID": 0,
|
|
||||||
"reason": ""
|
|
||||||
}
|
|
||||||
|
|
||||||
def handle(self, data):
|
|
||||||
data = super().parseData(data)
|
|
||||||
if data is None:
|
|
||||||
return
|
|
||||||
targetToken = glob.tokens.getTokenFromUserID(data["userID"])
|
|
||||||
if targetToken is not None:
|
|
||||||
targetToken.kick(data["reason"], "pubsub_kick")
|
|
|
@ -1,19 +0,0 @@
|
||||||
from common.redis import generalPubSubHandler
|
|
||||||
from objects import glob
|
|
||||||
from constants import serverPackets
|
|
||||||
|
|
||||||
class handler(generalPubSubHandler.generalPubSubHandler):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.structure = {
|
|
||||||
"userID": 0,
|
|
||||||
"message": ""
|
|
||||||
}
|
|
||||||
|
|
||||||
def handle(self, data):
|
|
||||||
data = super().parseData(data)
|
|
||||||
if data is None:
|
|
||||||
return
|
|
||||||
targetToken = glob.tokens.getTokenFromUserID(data["userID"])
|
|
||||||
if targetToken is not None:
|
|
||||||
targetToken.enqueue(serverPackets.notification(data["message"]))
|
|
|
@ -1,15 +0,0 @@
|
||||||
from common.redis import generalPubSubHandler
|
|
||||||
from objects import glob
|
|
||||||
|
|
||||||
class handler(generalPubSubHandler.generalPubSubHandler):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.type = "int"
|
|
||||||
|
|
||||||
def handle(self, userID):
|
|
||||||
userID = super().parseData(userID)
|
|
||||||
if userID is None:
|
|
||||||
return
|
|
||||||
targetToken = glob.tokens.getTokenFromUserID(userID)
|
|
||||||
if targetToken is not None:
|
|
||||||
targetToken.silence()
|
|
|
@ -1,15 +0,0 @@
|
||||||
from common.redis import generalPubSubHandler
|
|
||||||
from objects import glob
|
|
||||||
|
|
||||||
class handler(generalPubSubHandler.generalPubSubHandler):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.type = "int"
|
|
||||||
|
|
||||||
def handle(self, userID):
|
|
||||||
userID = super().parseData(userID)
|
|
||||||
if userID is None:
|
|
||||||
return
|
|
||||||
targetToken = glob.tokens.getTokenFromUserID(userID)
|
|
||||||
if targetToken is not None:
|
|
||||||
targetToken.updateCachedStats()
|
|
|
@ -1,10 +1,7 @@
|
||||||
requests==2.18.1
|
requests
|
||||||
tornado==4.4.2
|
tornado
|
||||||
mysqlclient==1.3.9
|
mysqlclient
|
||||||
psutil==5.2.2
|
psutil
|
||||||
raven==5.32.0
|
raven
|
||||||
bcrypt==3.1.1
|
bcrypt>=3.1.1
|
||||||
dill==0.2.7.1
|
dill
|
||||||
redis==2.10.5
|
|
||||||
cython==0.27.3
|
|
||||||
datadog==0.14.0
|
|
17
setup.py
17
setup.py
|
@ -1,17 +0,0 @@
|
||||||
"""Cython build file"""
|
|
||||||
from distutils.core import setup
|
|
||||||
from distutils.extension import Extension
|
|
||||||
from Cython.Build import cythonize
|
|
||||||
import os
|
|
||||||
|
|
||||||
cythonExt = []
|
|
||||||
for root, dirs, files in os.walk(os.getcwd()):
|
|
||||||
for file in files:
|
|
||||||
if file.endswith(".pyx") and ".pyenv" not in root: # im sorry
|
|
||||||
filePath = os.path.relpath(os.path.join(root, file))
|
|
||||||
cythonExt.append(Extension(filePath.replace("/", ".")[:-4], [filePath]))
|
|
||||||
|
|
||||||
setup(
|
|
||||||
name = "pep.pyx modules",
|
|
||||||
ext_modules = cythonize(cythonExt, nthreads = 4),
|
|
||||||
)
|
|
Loading…
Reference in New Issue
Block a user