Compare commits
181 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
0550222eed | ||
|
a0053de311 | ||
|
848dacf621 | ||
|
5111b6b05f | ||
|
9ad8d5779e | ||
|
71f5a8c4d0 | ||
|
768971e240 | ||
|
a248b4032c | ||
|
cb6d828fd9 | ||
|
925d55bb16 | ||
|
4e97c717f5 | ||
|
f11708d463 | ||
|
4c3427fd76 | ||
|
a3ab53e108 | ||
|
74cfadb261 | ||
|
c09c7b5d96 | ||
|
56c1dcc48a | ||
|
1b9224fc79 | ||
|
887836045d | ||
|
cb2d1e74f7 | ||
|
7f283d9aa2 | ||
|
333cfca806 | ||
|
1dc37ecb77 | ||
|
a456508ed3 | ||
|
66acc12099 | ||
|
a9fc15d524 | ||
|
02b08d3024 | ||
|
d8c6fa4993 | ||
|
fe05aa7ace | ||
|
79ecdbcd44 | ||
|
fd47bc6e32 | ||
|
1a4e952e4f | ||
|
2cd69a9a63 | ||
|
ea4e2bd4fd | ||
|
508f6b507a | ||
|
e89fbe7604 | ||
|
05694c5d87 | ||
|
198bdb9997 | ||
|
473a2e1f2d | ||
|
a809008e55 | ||
|
201593ea02 | ||
|
3275d31861 | ||
|
05eead0e1a | ||
|
8bdc56faf6 | ||
|
745a833aab | ||
|
dcad5c5736 | ||
|
c1f8ca8ed3 | ||
|
e0d54f49d1 | ||
|
c2af1b9772 | ||
|
a99bf0c74b | ||
|
f0fa00b181 | ||
|
956aa8161f | ||
|
08b367812f | ||
|
0722c91018 | ||
|
f0e8223b5c | ||
|
27a5f9c000 | ||
|
29db61fd12 | ||
|
e63a85e4a4 | ||
|
050c1d5fe8 | ||
|
90cd4634fd | ||
|
e3f1bc05e9 | ||
|
0939ec972c | ||
|
2ef89daf4c | ||
|
bb8ccf8c85 | ||
|
dc90f506bd | ||
|
2f9179362c | ||
|
3dfb1228ee | ||
|
c4123eb636 | ||
|
e3e46a34ec | ||
|
3ed837dc96 | ||
|
48534bb551 | ||
|
36d701eac1 | ||
|
60fe2bc56e | ||
|
b8baddf694 | ||
|
018da5c0a2 | ||
|
0a2ca07198 | ||
|
34e7a332e6 | ||
|
8d97227965 | ||
|
176775f8f3 | ||
|
0329847477 | ||
|
b24b4ee88d | ||
|
e40acd335b | ||
|
ce75f5ee99 | ||
|
29359ad4fd | ||
|
def4891008 | ||
|
5d73682a71 | ||
|
0229fc4e65 | ||
|
05c4c89002 | ||
|
02b266f229 | ||
|
612e808702 | ||
|
466004f239 | ||
|
5f279da6cf | ||
|
aa1887e2c4 | ||
|
df2a9bb13d | ||
|
e2149d7d61 | ||
|
ffc84448a2 | ||
|
933c92e5f2 | ||
|
3bb1029832 | ||
|
ce889e608a | ||
|
aeecccdd13 | ||
|
9425043b1e | ||
|
f660a20f60 | ||
|
5adc7f4261 | ||
|
3653447761 | ||
|
17aab9551e | ||
|
87d6186993 | ||
|
5975e84f52 | ||
|
3309f2f8fd | ||
|
baa8ae4cc2 | ||
|
837df03f05 | ||
|
c2a2d9c97b | ||
|
fc989a2705 | ||
|
310bc1d3b3 | ||
|
9f647d5f9e | ||
|
1e6ee91685 | ||
|
86995feb34 | ||
|
af554c94d5 | ||
|
3373bc9581 | ||
|
2cf1cdf1fd | ||
|
8c3fc6842d | ||
|
eab8bee828 | ||
|
66061d5fb2 | ||
|
401dd5ecdb | ||
|
d439490029 | ||
|
f4d0a2424d | ||
|
a177e65fcf | ||
|
c14c86fe0d | ||
|
25df2228e3 | ||
|
941cf81877 | ||
|
76bb15f9f3 | ||
|
8043d686c5 | ||
|
cae82bd107 | ||
|
2c2c85b382 | ||
|
a0fdc6c292 | ||
|
487f583083 | ||
|
530d0c3b74 | ||
|
46ec4f3704 | ||
|
0aa0ab0475 | ||
|
1c2a29a88e | ||
|
92e57aff28 | ||
|
7e535d7ed5 | ||
|
0464f713f7 | ||
|
66776c60e0 | ||
|
4ef98b5fc0 | ||
|
96add06b44 | ||
|
31971d4a8b | ||
|
9175f9e7f2 | ||
|
784c4a11f1 | ||
|
2407ecc3bf | ||
|
9880c5004d | ||
|
d2f111fd7b | ||
|
e489221e39 | ||
|
84f1fb566c | ||
|
fc3736eba8 | ||
|
768913da59 | ||
|
c6417c31ed | ||
|
9055fcaf5e | ||
|
f85640ae39 | ||
|
18a7c47db6 | ||
|
22ae4c332b | ||
|
8f156a0702 | ||
|
62b67da9fb | ||
|
501130721d | ||
|
e6cdef4580 | ||
|
00c544b7c7 | ||
|
ebf0e1d458 | ||
|
fd23cf2b2c | ||
|
8a8a4968a3 | ||
|
2ae3c5f701 | ||
|
f4c099c809 | ||
|
f8cc0c738c | ||
|
4557b08df8 | ||
|
49f8bd8cf1 | ||
|
7ba5db62b4 | ||
|
7f534f0984 | ||
|
daf457fc5c | ||
|
b4d498c26c | ||
|
44545c3bcb | ||
|
c4a6c84cec | ||
|
8532731f19 | ||
|
b836f77446 |
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -6,4 +6,5 @@ filters.txt
|
|||
.idea
|
||||
redistest.py
|
||||
*.c
|
||||
*.so
|
||||
*.so
|
||||
.pyenv
|
2
.gitmodules
vendored
2
.gitmodules
vendored
|
@ -1,3 +1,3 @@
|
|||
[submodule "common"]
|
||||
path = common
|
||||
url = git@git.zxq.co:ripple/ripple-python-common.git
|
||||
url = https://github.com/osufx/ripple-python-common.git
|
||||
|
|
7
.landscape.yaml
Normal file
7
.landscape.yaml
Normal file
|
@ -0,0 +1,7 @@
|
|||
python-targets:
|
||||
- 3
|
||||
pep8:
|
||||
none: true
|
||||
pylint:
|
||||
disable:
|
||||
- cyclic-import
|
|
@ -1,4 +1,4 @@
|
|||
## pep.py
|
||||
## pep.py [![Code Health](https://landscape.io/github/osuripple/pep.py/master/landscape.svg?style=flat)](https://landscape.io/github/osuripple/pep.py/master)
|
||||
|
||||
- Origin: https://git.zxq.co/ripple/pep.py
|
||||
- Mirror: https://github.com/osuripple/pep.py
|
||||
|
|
2
common
2
common
|
@ -1 +1 @@
|
|||
Subproject commit 3288420cd81cdb84912b9debda1e7828ebb7c62a
|
||||
Subproject commit 6103fe96a79cd8f5cbabe24b5fac9cf2a5cacb4a
|
|
@ -143,6 +143,26 @@ def transferHost(stream):
|
|||
def matchInvite(stream):
|
||||
return packetHelper.readPacketData(stream, [["userID", dataTypes.UINT32]])
|
||||
|
||||
def matchFrames(stream):
|
||||
return packetHelper.readPacketData(stream,
|
||||
[
|
||||
["time", dataTypes.SINT32],
|
||||
["id", dataTypes.BYTE],
|
||||
["count300", dataTypes.UINT16],
|
||||
["count100", dataTypes.UINT16],
|
||||
["count50", dataTypes.UINT16],
|
||||
["countGeki", dataTypes.UINT16],
|
||||
["countKatu", dataTypes.UINT16],
|
||||
["countMiss", dataTypes.UINT16],
|
||||
["totalScore", dataTypes.SINT32],
|
||||
["maxCombo", dataTypes.UINT16],
|
||||
["currentCombo", dataTypes.UINT16],
|
||||
["perfect", dataTypes.BYTE],
|
||||
["currentHp", dataTypes.BYTE],
|
||||
["tagByte", dataTypes.BYTE],
|
||||
["usingScoreV2", dataTypes.BYTE]
|
||||
])
|
||||
|
||||
def tournamentMatchInfoRequest(stream):
|
||||
return packetHelper.readPacketData(stream, [["matchID", dataTypes.UINT32]])
|
||||
|
||||
|
|
|
@ -89,4 +89,22 @@ class unknownStreamException(Exception):
|
|||
pass
|
||||
|
||||
class userTournamentException(Exception):
|
||||
pass
|
||||
|
||||
class userAlreadyInChannelException(Exception):
|
||||
pass
|
||||
|
||||
class userNotInChannelException(Exception):
|
||||
pass
|
||||
|
||||
class missingReportInfoException(Exception):
|
||||
pass
|
||||
|
||||
class invalidUserException(Exception):
|
||||
pass
|
||||
|
||||
class wrongChannelException(Exception):
|
||||
pass
|
||||
|
||||
class periodicLoopException(Exception):
|
||||
pass
|
|
@ -1,19 +1,34 @@
|
|||
import json
|
||||
import random
|
||||
import re
|
||||
import threading
|
||||
|
||||
import requests
|
||||
import time
|
||||
|
||||
from common import generalUtils
|
||||
from common.constants import mods
|
||||
from common.log import logUtils as log
|
||||
from common.ripple import userUtils
|
||||
from constants import exceptions
|
||||
from constants import exceptions, slotStatuses, matchModModes, matchTeams, matchTeamTypes, matchScoringTypes
|
||||
from common.constants import gameModes
|
||||
from common.constants import privileges
|
||||
from constants import serverPackets
|
||||
from helpers import systemHelper
|
||||
from objects import fokabot
|
||||
from objects import glob
|
||||
from helpers import chatHelper as chat
|
||||
from common.web import cheesegull
|
||||
|
||||
|
||||
def bloodcatMessage(beatmapID):
|
||||
beatmap = glob.db.fetch("SELECT song_name, beatmapset_id FROM beatmaps WHERE beatmap_id = %s LIMIT 1", [beatmapID])
|
||||
if beatmap is None:
|
||||
return "Sorry, I'm not able to provide a download link for this map :("
|
||||
return "Download [https://bloodcat.com/osu/s/{} {}] from Bloodcat".format(
|
||||
beatmap["beatmapset_id"],
|
||||
beatmap["song_name"],
|
||||
)
|
||||
|
||||
"""
|
||||
Commands callbacks
|
||||
|
@ -31,38 +46,19 @@ TODO: Change False to None, because False doesn't make any sense
|
|||
"""
|
||||
def instantRestart(fro, chan, message):
|
||||
glob.streams.broadcast("main", serverPackets.notification("We are restarting Bancho. Be right back!"))
|
||||
systemHelper.scheduleShutdown(0, True, delay=1)
|
||||
systemHelper.scheduleShutdown(0, True, delay=5)
|
||||
return False
|
||||
|
||||
def faq(fro, chan, message):
|
||||
# TODO: Unhardcode this
|
||||
if message[0] == "rules":
|
||||
return "Please make sure to check (Ripple's rules)[http://ripple.moe/?p=23]."
|
||||
elif message[0] == "swearing":
|
||||
return "Please don't abuse swearing"
|
||||
elif message[0] == "spam":
|
||||
return "Please don't spam"
|
||||
elif message[0] == "offend":
|
||||
return "Please don't offend other players"
|
||||
elif message[0] == "github":
|
||||
return "(Ripple's Github page!)[https://github.com/osuripple/ripple]"
|
||||
elif message[0] == "discord":
|
||||
return "(Join Ripple's Discord!)[https://discord.gg/0rJcZruIsA6rXuIx]"
|
||||
elif message[0] == "blog":
|
||||
return "You can find the latest Ripple news on the (blog)[https://ripple.moe/blog/]!"
|
||||
elif message[0] == "changelog":
|
||||
return "Check the (changelog)[https://ripple.moe/index.php?p=17] !"
|
||||
elif message[0] == "status":
|
||||
return "Check the server status (here!)[https://ripple.moe/index.php?p=27]"
|
||||
elif message[0] == "english":
|
||||
return "Please keep this channel in english."
|
||||
else:
|
||||
key = message[0].lower()
|
||||
if key not in glob.conf.extra["pep.py"]["faq"]:
|
||||
return False
|
||||
return glob.conf.extra["pep.py"]["faq"][key]
|
||||
|
||||
def roll(fro, chan, message):
|
||||
maxPoints = 100
|
||||
if len(message) >= 1:
|
||||
if message[0].isdigit() == True and int(message[0]) > 0:
|
||||
if message[0].isdigit() and int(message[0]) > 0:
|
||||
maxPoints = int(message[0])
|
||||
|
||||
points = random.randrange(0,maxPoints)
|
||||
|
@ -72,15 +68,20 @@ def roll(fro, chan, message):
|
|||
# return random.choice(["yes", "no", "maybe"])
|
||||
|
||||
def alert(fro, chan, message):
|
||||
glob.streams.broadcast("main", serverPackets.notification(' '.join(message[:])))
|
||||
msg = ' '.join(message[:]).strip()
|
||||
if not msg:
|
||||
return False
|
||||
glob.streams.broadcast("main", serverPackets.notification(msg))
|
||||
return False
|
||||
|
||||
def alertUser(fro, chan, message):
|
||||
target = message[0].replace("_", " ")
|
||||
|
||||
targetToken = glob.tokens.getTokenFromUsername(target)
|
||||
target = message[0].lower()
|
||||
targetToken = glob.tokens.getTokenFromUsername(userUtils.safeUsername(target), safe=True)
|
||||
if targetToken is not None:
|
||||
targetToken.enqueue(serverPackets.notification(' '.join(message[1:])))
|
||||
msg = ' '.join(message[1:]).strip()
|
||||
if not msg:
|
||||
return False
|
||||
targetToken.enqueue(serverPackets.notification(msg))
|
||||
return False
|
||||
else:
|
||||
return "User offline."
|
||||
|
@ -106,9 +107,10 @@ def moderated(fro, chan, message):
|
|||
def kickAll(fro, chan, message):
|
||||
# Kick everyone but mods/admins
|
||||
toKick = []
|
||||
for key, value in glob.tokens.tokens.items():
|
||||
if not value.admin:
|
||||
toKick.append(key)
|
||||
with glob.tokens:
|
||||
for key, value in glob.tokens.tokens.items():
|
||||
if not value.admin:
|
||||
toKick.append(key)
|
||||
|
||||
# Loop though users to kick (we can't change dictionary size while iterating)
|
||||
for i in toKick:
|
||||
|
@ -119,15 +121,18 @@ def kickAll(fro, chan, message):
|
|||
|
||||
def kick(fro, chan, message):
|
||||
# Get parameters
|
||||
target = message[0].replace("_", " ")
|
||||
target = message[0].lower()
|
||||
if target == glob.BOT_NAME.lower():
|
||||
return "Nope."
|
||||
|
||||
# Get target token and make sure is connected
|
||||
targetToken = glob.tokens.getTokenFromUsername(target)
|
||||
if targetToken is None:
|
||||
tokens = glob.tokens.getTokenFromUsername(userUtils.safeUsername(target), safe=True, _all=True)
|
||||
if len(tokens) == 0:
|
||||
return "{} is not online".format(target)
|
||||
|
||||
# Kick user
|
||||
targetToken.kick()
|
||||
# Kick users
|
||||
for i in tokens:
|
||||
i.kick()
|
||||
|
||||
# Bot response
|
||||
return "{} has been kicked from the server.".format(target)
|
||||
|
@ -135,22 +140,25 @@ def kick(fro, chan, message):
|
|||
def fokabotReconnect(fro, chan, message):
|
||||
# Check if fokabot is already connected
|
||||
if glob.tokens.getTokenFromUserID(999) is not None:
|
||||
return "Fokabot is already connected to Bancho"
|
||||
return "{} is already connected to Bancho".format(glob.BOT_NAME)
|
||||
|
||||
# Fokabot is not connected, connect it
|
||||
fokabot.connect()
|
||||
return False
|
||||
|
||||
def silence(fro, chan, message):
|
||||
for i in message:
|
||||
i = i.lower()
|
||||
target = message[0].replace("_", " ")
|
||||
message = [x.lower() for x in message]
|
||||
target = message[0]
|
||||
amount = message[1]
|
||||
unit = message[2]
|
||||
reason = ' '.join(message[3:])
|
||||
reason = ' '.join(message[3:]).strip()
|
||||
if not reason:
|
||||
return "Please provide a valid reason."
|
||||
if not amount.isdigit():
|
||||
return "The amount must be a number."
|
||||
|
||||
# Get target user ID
|
||||
targetUserID = userUtils.getID(target)
|
||||
targetUserID = userUtils.getIDSafe(target)
|
||||
userID = userUtils.getID(fro)
|
||||
|
||||
# Make sure the user exists
|
||||
|
@ -174,7 +182,7 @@ def silence(fro, chan, message):
|
|||
return "Invalid silence time. Max silence time is 7 days."
|
||||
|
||||
# Send silence packet to target if he's connected
|
||||
targetToken = glob.tokens.getTokenFromUsername(target)
|
||||
targetToken = glob.tokens.getTokenFromUsername(userUtils.safeUsername(target), safe=True)
|
||||
if targetToken is not None:
|
||||
# user online, silence both in db and with packet
|
||||
targetToken.silence(silenceTime, reason, userID)
|
||||
|
@ -190,16 +198,16 @@ def removeSilence(fro, chan, message):
|
|||
# Get parameters
|
||||
for i in message:
|
||||
i = i.lower()
|
||||
target = message[0].replace("_", " ")
|
||||
target = message[0]
|
||||
|
||||
# Make sure the user exists
|
||||
targetUserID = userUtils.getID(target)
|
||||
targetUserID = userUtils.getIDSafe(target)
|
||||
userID = userUtils.getID(fro)
|
||||
if not targetUserID:
|
||||
return "{}: user not found".format(target)
|
||||
|
||||
# Send new silence end packet to user if he's online
|
||||
targetToken = glob.tokens.getTokenFromUsername(target)
|
||||
targetToken = glob.tokens.getTokenFromUsername(userUtils.safeUsername(target), safe=True)
|
||||
if targetToken is not None:
|
||||
# User online, remove silence both in db and with packet
|
||||
targetToken.silence(0, "", userID)
|
||||
|
@ -213,10 +221,10 @@ def ban(fro, chan, message):
|
|||
# Get parameters
|
||||
for i in message:
|
||||
i = i.lower()
|
||||
target = message[0].replace("_", " ")
|
||||
target = message[0]
|
||||
|
||||
# Make sure the user exists
|
||||
targetUserID = userUtils.getID(target)
|
||||
targetUserID = userUtils.getIDSafe(target)
|
||||
userID = userUtils.getID(fro)
|
||||
if not targetUserID:
|
||||
return "{}: user not found".format(target)
|
||||
|
@ -225,7 +233,7 @@ def ban(fro, chan, message):
|
|||
userUtils.ban(targetUserID)
|
||||
|
||||
# Send ban packet to the user if he's online
|
||||
targetToken = glob.tokens.getTokenFromUsername(target)
|
||||
targetToken = glob.tokens.getTokenFromUsername(userUtils.safeUsername(target), safe=True)
|
||||
if targetToken is not None:
|
||||
targetToken.enqueue(serverPackets.loginBanned())
|
||||
|
||||
|
@ -236,10 +244,10 @@ def unban(fro, chan, message):
|
|||
# Get parameters
|
||||
for i in message:
|
||||
i = i.lower()
|
||||
target = message[0].replace("_", " ")
|
||||
target = message[0]
|
||||
|
||||
# Make sure the user exists
|
||||
targetUserID = userUtils.getID(target)
|
||||
targetUserID = userUtils.getIDSafe(target)
|
||||
userID = userUtils.getID(fro)
|
||||
if not targetUserID:
|
||||
return "{}: user not found".format(target)
|
||||
|
@ -254,10 +262,10 @@ def restrict(fro, chan, message):
|
|||
# Get parameters
|
||||
for i in message:
|
||||
i = i.lower()
|
||||
target = message[0].replace("_", " ")
|
||||
target = message[0]
|
||||
|
||||
# Make sure the user exists
|
||||
targetUserID = userUtils.getID(target)
|
||||
targetUserID = userUtils.getIDSafe(target)
|
||||
userID = userUtils.getID(fro)
|
||||
if not targetUserID:
|
||||
return "{}: user not found".format(target)
|
||||
|
@ -266,7 +274,7 @@ def restrict(fro, chan, message):
|
|||
userUtils.restrict(targetUserID)
|
||||
|
||||
# Send restricted mode packet to this user if he's online
|
||||
targetToken = glob.tokens.getTokenFromUsername(target)
|
||||
targetToken = glob.tokens.getTokenFromUsername(userUtils.safeUsername(target), safe=True)
|
||||
if targetToken is not None:
|
||||
targetToken.setRestricted()
|
||||
|
||||
|
@ -277,10 +285,10 @@ def unrestrict(fro, chan, message):
|
|||
# Get parameters
|
||||
for i in message:
|
||||
i = i.lower()
|
||||
target = message[0].replace("_", " ")
|
||||
target = message[0]
|
||||
|
||||
# Make sure the user exists
|
||||
targetUserID = userUtils.getID(target)
|
||||
targetUserID = userUtils.getIDSafe(target)
|
||||
userID = userUtils.getID(fro)
|
||||
if not targetUserID:
|
||||
return "{}: user not found".format(target)
|
||||
|
@ -325,9 +333,10 @@ def systemMaintenance(fro, chan, message):
|
|||
who = []
|
||||
|
||||
# Disconnect everyone but mod/admins
|
||||
for _, value in glob.tokens.tokens.items():
|
||||
if not value.admin:
|
||||
who.append(value.userID)
|
||||
with glob.tokens:
|
||||
for _, value in glob.tokens.tokens.items():
|
||||
if not value.admin:
|
||||
who.append(value.userID)
|
||||
|
||||
glob.streams.broadcast("main", serverPackets.notification("Our bancho server is in maintenance mode. Please try to login again later."))
|
||||
glob.tokens.multipleEnqueue(serverPackets.loginError(), who)
|
||||
|
@ -380,7 +389,7 @@ def getPPMessage(userID, just_data = False):
|
|||
currentAcc = token.tillerino[2]
|
||||
|
||||
# Send request to LETS api
|
||||
resp = requests.get("http://127.0.0.1:5002/api/v1/pp?b={}&m={}".format(currentMap, currentMods, currentAcc), timeout=10).text
|
||||
resp = requests.get("http://127.0.0.1:5002/api/v1/pp?b={}&m={}".format(currentMap, currentMods), timeout=10).text
|
||||
data = json.loads(resp)
|
||||
|
||||
# Make sure status is in response data
|
||||
|
@ -390,7 +399,7 @@ def getPPMessage(userID, just_data = False):
|
|||
# Make sure status is 200
|
||||
if data["status"] != 200:
|
||||
if "message" in data:
|
||||
return "Error in LETS API call ({}). Please tell this to a dev.".format(data["message"])
|
||||
return "Error in LETS API call ({}).".format(data["message"])
|
||||
else:
|
||||
raise exceptions.apiException
|
||||
|
||||
|
@ -426,7 +435,7 @@ def getPPMessage(userID, just_data = False):
|
|||
return "API Timeout. Please try again in a few seconds."
|
||||
except exceptions.apiException:
|
||||
# API error
|
||||
return "Unknown error in LETS API call. Please tell this to a dev."
|
||||
return "Unknown error in LETS API call."
|
||||
#except:
|
||||
# Unknown exception
|
||||
# TODO: print exception
|
||||
|
@ -434,6 +443,14 @@ def getPPMessage(userID, just_data = False):
|
|||
|
||||
def tillerinoNp(fro, chan, message):
|
||||
try:
|
||||
# Bloodcat trigger for #spect_
|
||||
if chan.startswith("#spect_"):
|
||||
spectatorHostUserID = getSpectatorHostUserIDFromChannel(chan)
|
||||
spectatorHostToken = glob.tokens.getTokenFromUserID(spectatorHostUserID, ignoreIRC=True)
|
||||
if spectatorHostToken is None:
|
||||
return False
|
||||
return bloodcatMessage(spectatorHostToken.beatmapID)
|
||||
|
||||
# Run the command in PM only
|
||||
if chan.startswith("#"):
|
||||
return False
|
||||
|
@ -564,6 +581,10 @@ def tillerinoAcc(fro, chan, message):
|
|||
|
||||
def tillerinoLast(fro, chan, message):
|
||||
try:
|
||||
# Run the command in PM only
|
||||
if chan.startswith("#"):
|
||||
return False
|
||||
|
||||
data = glob.db.fetch("""SELECT beatmaps.song_name as sn, scores.*,
|
||||
beatmaps.beatmap_id as bid, beatmaps.difficulty_std, beatmaps.difficulty_taiko, beatmaps.difficulty_ctb, beatmaps.difficulty_mania, beatmaps.max_combo as fc
|
||||
FROM scores
|
||||
|
@ -579,11 +600,11 @@ def tillerinoLast(fro, chan, message):
|
|||
rank = generalUtils.getRank(data["play_mode"], data["mods"], data["accuracy"],
|
||||
data["300_count"], data["100_count"], data["50_count"], data["misses_count"])
|
||||
|
||||
ifPlayer = "{0} | ".format(fro) if chan != "FokaBot" else ""
|
||||
ifPlayer = "{0} | ".format(fro) if chan != glob.BOT_NAME else ""
|
||||
ifFc = " (FC)" if data["max_combo"] == data["fc"] else " {0}x/{1}x".format(data["max_combo"], data["fc"])
|
||||
beatmapLink = "[http://osu.ppy.sh/b/{1} {0}]".format(data["sn"], data["bid"])
|
||||
|
||||
hasPP = data["play_mode"] == gameModes.STD or data["play_mode"] == gameModes.MANIA
|
||||
hasPP = data["play_mode"] != gameModes.CTB
|
||||
|
||||
msg = ifPlayer
|
||||
msg += beatmapLink
|
||||
|
@ -656,7 +677,7 @@ def pp(fro, chan, message):
|
|||
pp = userUtils.getPP(token.userID, gameMode)
|
||||
return "You have {:,} pp".format(pp)
|
||||
|
||||
def updateBeatmap(fro, chan, to):
|
||||
def updateBeatmap(fro, chan, message):
|
||||
try:
|
||||
# Run the command in PM only
|
||||
if chan.startswith("#"):
|
||||
|
@ -671,26 +692,515 @@ def updateBeatmap(fro, chan, to):
|
|||
if token.tillerino[0] == 0:
|
||||
return "Please give me a beatmap first with /np command."
|
||||
|
||||
# Send request
|
||||
beatmapData = glob.db.fetch("SELECT beatmapset_id, song_name FROM beatmaps WHERE beatmap_id = %s LIMIT 1", [token.tillerino[0]])
|
||||
if beatmapData is None:
|
||||
return "Couldn't find beatmap data in database. Please load the beatmap's leaderboard and try again."
|
||||
|
||||
response = requests.post("{}/api/v1/update_beatmap".format(glob.conf.config["mirror"]["url"]), {
|
||||
"beatmap_set_id": beatmapData["beatmapset_id"],
|
||||
"beatmap_name": beatmapData["song_name"],
|
||||
"username": token.username,
|
||||
"key": glob.conf.config["mirror"]["apikey"]
|
||||
})
|
||||
if response.status_code == 200:
|
||||
return "An update request for that beatmap has been queued. You'll receive a message once the beatmap has been updated on our mirror!"
|
||||
elif response.status_code == 429:
|
||||
return "You are sending too many beatmaps update requests. Wait a bit and retry later."
|
||||
# Send the request to cheesegull
|
||||
ok, message = cheesegull.updateBeatmap(token.tillerino[0])
|
||||
if ok:
|
||||
return "An update request for that beatmap has been queued. Check back in a few minutes and the beatmap should be updated!"
|
||||
else:
|
||||
return "Error in beatmap mirror API request. Tell this to a dev: {}".format(response.text)
|
||||
return "Error in beatmap mirror API request: {}".format(message)
|
||||
except:
|
||||
return False
|
||||
|
||||
def report(fro, chan, message):
|
||||
msg = ""
|
||||
try:
|
||||
# TODO: Rate limit
|
||||
# Regex on message
|
||||
reportRegex = re.compile("^(.+) \((.+)\)\:(?: )?(.+)?$")
|
||||
result = reportRegex.search(" ".join(message))
|
||||
|
||||
# Make sure the message matches the regex
|
||||
if result is None:
|
||||
raise exceptions.invalidArgumentsException()
|
||||
|
||||
# Get username, report reason and report info
|
||||
target, reason, additionalInfo = result.groups()
|
||||
target = chat.fixUsernameForBancho(target)
|
||||
|
||||
# Make sure the target is not foka
|
||||
if target.lower() == glob.BOT_NAME.lower():
|
||||
raise exceptions.invalidUserException()
|
||||
|
||||
# Make sure the user exists
|
||||
targetID = userUtils.getID(target)
|
||||
if targetID == 0:
|
||||
raise exceptions.userNotFoundException()
|
||||
|
||||
# Make sure that the user has specified additional info if report reason is 'Other'
|
||||
if reason.lower() == "other" and additionalInfo is None:
|
||||
raise exceptions.missingReportInfoException()
|
||||
|
||||
# Get the token if possible
|
||||
chatlog = ""
|
||||
token = glob.tokens.getTokenFromUsername(userUtils.safeUsername(target), safe=True)
|
||||
if token is not None:
|
||||
chatlog = token.getMessagesBufferString()
|
||||
|
||||
# Everything is fine, submit report
|
||||
glob.db.execute("INSERT INTO reports (id, from_uid, to_uid, reason, chatlog, time) VALUES (NULL, %s, %s, %s, %s, %s)", [userUtils.getID(fro), targetID, "{reason} - ingame {info}".format(reason=reason, info="({})".format(additionalInfo) if additionalInfo is not None else ""), chatlog, int(time.time())])
|
||||
msg = "You've reported {target} for {reason}{info}. A Community Manager will check your report as soon as possible. Every !report message you may see in chat wasn't sent to anyone, so nobody in chat, but admins, know about your report. Thank you for reporting!".format(target=target, reason=reason, info="" if additionalInfo is None else " (" + additionalInfo + ")")
|
||||
adminMsg = "{user} has reported {target} for {reason} ({info})".format(user=fro, target=target, reason=reason, info=additionalInfo)
|
||||
|
||||
# Log report in #admin and on discord
|
||||
chat.sendMessage(glob.BOT_NAME, "#admin", adminMsg)
|
||||
log.warning(adminMsg, discord="cm")
|
||||
except exceptions.invalidUserException:
|
||||
msg = "Hello, {} here! You can't report me. I won't forget what you've tried to do. Watch out.".format(glob.BOT_NAME)
|
||||
except exceptions.invalidArgumentsException:
|
||||
msg = "Invalid report command syntax. To report an user, click on it and select 'Report user'."
|
||||
except exceptions.userNotFoundException:
|
||||
msg = "The user you've tried to report doesn't exist."
|
||||
except exceptions.missingReportInfoException:
|
||||
msg = "Please specify the reason of your report."
|
||||
except:
|
||||
raise
|
||||
finally:
|
||||
if msg != "":
|
||||
token = glob.tokens.getTokenFromUsername(fro)
|
||||
if token is not None:
|
||||
if token.irc:
|
||||
chat.sendMessage(glob.BOT_NAME, fro, msg)
|
||||
else:
|
||||
token.enqueue(serverPackets.notification(msg))
|
||||
return False
|
||||
|
||||
def getMatchIDFromChannel(chan):
|
||||
if not chan.lower().startswith("#multi_"):
|
||||
raise exceptions.wrongChannelException()
|
||||
parts = chan.lower().split("_")
|
||||
if len(parts) < 2 or not parts[1].isdigit():
|
||||
raise exceptions.wrongChannelException()
|
||||
matchID = int(parts[1])
|
||||
if matchID not in glob.matches.matches:
|
||||
raise exceptions.matchNotFoundException()
|
||||
return matchID
|
||||
|
||||
def getSpectatorHostUserIDFromChannel(chan):
|
||||
if not chan.lower().startswith("#spect_"):
|
||||
raise exceptions.wrongChannelException()
|
||||
parts = chan.lower().split("_")
|
||||
if len(parts) < 2 or not parts[1].isdigit():
|
||||
raise exceptions.wrongChannelException()
|
||||
userID = int(parts[1])
|
||||
return userID
|
||||
|
||||
def multiplayer(fro, chan, message):
|
||||
def mpMake():
|
||||
if len(message) < 2:
|
||||
raise exceptions.invalidArgumentsException("Wrong syntax: !mp make <name>")
|
||||
matchName = " ".join(message[1:]).strip()
|
||||
if not matchName:
|
||||
raise exceptions.invalidArgumentsException("Match name must not be empty!")
|
||||
matchID = glob.matches.createMatch(matchName, generalUtils.stringMd5(generalUtils.randomString(32)), 0, "Tournament", "", 0, -1, isTourney=True)
|
||||
glob.matches.matches[matchID].sendUpdates()
|
||||
return "Tourney match #{} created!".format(matchID)
|
||||
|
||||
def mpJoin():
|
||||
if len(message) < 2 or not message[1].isdigit():
|
||||
raise exceptions.invalidArgumentsException("Wrong syntax: !mp join <id>")
|
||||
matchID = int(message[1])
|
||||
userToken = glob.tokens.getTokenFromUsername(fro, ignoreIRC=True)
|
||||
if userToken is None:
|
||||
raise exceptions.invalidArgumentsException(
|
||||
"No game clients found for {}, can't join the match. "
|
||||
"If you're a referee and you want to join the chat "
|
||||
"channel from IRC, use /join #multi_{} instead.".format(fro, matchID)
|
||||
)
|
||||
userToken.joinMatch(matchID)
|
||||
return "Attempting to join match #{}!".format(matchID)
|
||||
|
||||
def mpClose():
|
||||
matchID = getMatchIDFromChannel(chan)
|
||||
glob.matches.disposeMatch(matchID)
|
||||
return "Multiplayer match #{} disposed successfully".format(matchID)
|
||||
|
||||
def mpLock():
|
||||
matchID = getMatchIDFromChannel(chan)
|
||||
glob.matches.matches[matchID].isLocked = True
|
||||
return "This match has been locked"
|
||||
|
||||
def mpUnlock():
|
||||
matchID = getMatchIDFromChannel(chan)
|
||||
glob.matches.matches[matchID].isLocked = False
|
||||
return "This match has been unlocked"
|
||||
|
||||
def mpSize():
|
||||
if len(message) < 2 or not message[1].isdigit() or int(message[1]) < 2 or int(message[1]) > 16:
|
||||
raise exceptions.invalidArgumentsException("Wrong syntax: !mp size <slots(2-16)>")
|
||||
matchSize = int(message[1])
|
||||
_match = glob.matches.matches[getMatchIDFromChannel(chan)]
|
||||
_match.forceSize(matchSize)
|
||||
return "Match size changed to {}".format(matchSize)
|
||||
|
||||
def mpMove():
|
||||
if len(message) < 3 or not message[2].isdigit() or int(message[2]) < 0 or int(message[2]) > 16:
|
||||
raise exceptions.invalidArgumentsException("Wrong syntax: !mp move <username> <slot>")
|
||||
username = message[1]
|
||||
newSlotID = int(message[2])
|
||||
userID = userUtils.getIDSafe(username)
|
||||
if userID is None:
|
||||
raise exceptions.userNotFoundException("No such user")
|
||||
_match = glob.matches.matches[getMatchIDFromChannel(chan)]
|
||||
success = _match.userChangeSlot(userID, newSlotID)
|
||||
if success:
|
||||
result = "Player {} moved to slot {}".format(username, newSlotID)
|
||||
else:
|
||||
result = "You can't use that slot: it's either already occupied by someone else or locked"
|
||||
return result
|
||||
|
||||
def mpHost():
|
||||
if len(message) < 2:
|
||||
raise exceptions.invalidArgumentsException("Wrong syntax: !mp host <username>")
|
||||
username = message[1].strip()
|
||||
if not username:
|
||||
raise exceptions.invalidArgumentsException("Please provide a username")
|
||||
userID = userUtils.getIDSafe(username)
|
||||
if userID is None:
|
||||
raise exceptions.userNotFoundException("No such user")
|
||||
_match = glob.matches.matches[getMatchIDFromChannel(chan)]
|
||||
success = _match.setHost(userID)
|
||||
return "{} is now the host".format(username) if success else "Couldn't give host to {}".format(username)
|
||||
|
||||
def mpClearHost():
|
||||
matchID = getMatchIDFromChannel(chan)
|
||||
glob.matches.matches[matchID].removeHost()
|
||||
return "Host has been removed from this match"
|
||||
|
||||
def mpStart():
|
||||
def _start():
|
||||
matchID = getMatchIDFromChannel(chan)
|
||||
success = glob.matches.matches[matchID].start()
|
||||
if not success:
|
||||
chat.sendMessage(glob.BOT_NAME, chan, "Couldn't start match. Make sure there are enough players and "
|
||||
"teams are valid. The match has been unlocked.")
|
||||
else:
|
||||
chat.sendMessage(glob.BOT_NAME, chan, "Have fun!")
|
||||
|
||||
|
||||
def _decreaseTimer(t):
|
||||
if t <= 0:
|
||||
_start()
|
||||
else:
|
||||
if t % 10 == 0 or t <= 5:
|
||||
chat.sendMessage(glob.BOT_NAME, chan, "Match starts in {} seconds.".format(t))
|
||||
threading.Timer(1.00, _decreaseTimer, [t - 1]).start()
|
||||
|
||||
if len(message) < 2 or not message[1].isdigit():
|
||||
startTime = 0
|
||||
else:
|
||||
startTime = int(message[1])
|
||||
|
||||
force = False if len(message) < 3 else message[2].lower() == "force"
|
||||
_match = glob.matches.matches[getMatchIDFromChannel(chan)]
|
||||
|
||||
# Force everyone to ready
|
||||
someoneNotReady = False
|
||||
for i, slot in enumerate(_match.slots):
|
||||
if slot.status != slotStatuses.READY and slot.user is not None:
|
||||
someoneNotReady = True
|
||||
if force:
|
||||
_match.toggleSlotReady(i)
|
||||
|
||||
if someoneNotReady and not force:
|
||||
return "Some users aren't ready yet. Use '!mp start force' if you want to start the match, " \
|
||||
"even with non-ready players."
|
||||
|
||||
if startTime == 0:
|
||||
_start()
|
||||
return "Starting match"
|
||||
else:
|
||||
_match.isStarting = True
|
||||
threading.Timer(1.00, _decreaseTimer, [startTime - 1]).start()
|
||||
return "Match starts in {} seconds. The match has been locked. " \
|
||||
"Please don't leave the match during the countdown " \
|
||||
"or you might receive a penalty.".format(startTime)
|
||||
|
||||
def mpInvite():
|
||||
if len(message) < 2:
|
||||
raise exceptions.invalidArgumentsException("Wrong syntax: !mp invite <username>")
|
||||
username = message[1].strip()
|
||||
if not username:
|
||||
raise exceptions.invalidArgumentsException("Please provide a username")
|
||||
userID = userUtils.getIDSafe(username)
|
||||
if userID is None:
|
||||
raise exceptions.userNotFoundException("No such user")
|
||||
token = glob.tokens.getTokenFromUserID(userID, ignoreIRC=True)
|
||||
if token is None:
|
||||
raise exceptions.invalidUserException("That user is not connected to bancho right now.")
|
||||
_match = glob.matches.matches[getMatchIDFromChannel(chan)]
|
||||
_match.invite(999, userID)
|
||||
token.enqueue(serverPackets.notification("Please accept the invite you've just received from {} to "
|
||||
"enter your tourney match.".format(glob.BOT_NAME)))
|
||||
return "An invite to this match has been sent to {}".format(username)
|
||||
|
||||
def mpMap():
|
||||
if len(message) < 2 or not message[1].isdigit() or (len(message) == 3 and not message[2].isdigit()):
|
||||
raise exceptions.invalidArgumentsException("Wrong syntax: !mp map <beatmapid> [<gamemode>]")
|
||||
beatmapID = int(message[1])
|
||||
gameMode = int(message[2]) if len(message) == 3 else 0
|
||||
if gameMode < 0 or gameMode > 3:
|
||||
raise exceptions.invalidArgumentsException("Gamemode must be 0, 1, 2 or 3")
|
||||
beatmapData = glob.db.fetch("SELECT * FROM beatmaps WHERE beatmap_id = %s LIMIT 1", [beatmapID])
|
||||
if beatmapData is None:
|
||||
raise exceptions.invalidArgumentsException("The beatmap you've selected couldn't be found in the database."
|
||||
"If the beatmap id is valid, please load the scoreboard first in "
|
||||
"order to cache it, then try again.")
|
||||
_match = glob.matches.matches[getMatchIDFromChannel(chan)]
|
||||
_match.beatmapID = beatmapID
|
||||
_match.beatmapName = beatmapData["song_name"]
|
||||
_match.beatmapMD5 = beatmapData["beatmap_md5"]
|
||||
_match.gameMode = gameMode
|
||||
_match.resetReady()
|
||||
_match.sendUpdates()
|
||||
return "Match map has been updated"
|
||||
|
||||
def mpSet():
|
||||
if len(message) < 2 or not message[1].isdigit() or \
|
||||
(len(message) >= 3 and not message[2].isdigit()) or \
|
||||
(len(message) >= 4 and not message[3].isdigit()):
|
||||
raise exceptions.invalidArgumentsException("Wrong syntax: !mp set <teammode> [<scoremode>] [<size>]")
|
||||
_match = glob.matches.matches[getMatchIDFromChannel(chan)]
|
||||
matchTeamType = int(message[1])
|
||||
matchScoringType = int(message[2]) if len(message) >= 3 else _match.matchScoringType
|
||||
if not 0 <= matchTeamType <= 3:
|
||||
raise exceptions.invalidArgumentsException("Match team type must be between 0 and 3")
|
||||
if not 0 <= matchScoringType <= 3:
|
||||
raise exceptions.invalidArgumentsException("Match scoring type must be between 0 and 3")
|
||||
oldMatchTeamType = _match.matchTeamType
|
||||
_match.matchTeamType = matchTeamType
|
||||
_match.matchScoringType = matchScoringType
|
||||
if len(message) >= 4:
|
||||
_match.forceSize(int(message[3]))
|
||||
if _match.matchTeamType != oldMatchTeamType:
|
||||
_match.initializeTeams()
|
||||
if _match.matchTeamType == matchTeamTypes.TAG_COOP or _match.matchTeamType == matchTeamTypes.TAG_TEAM_VS:
|
||||
_match.matchModMode = matchModModes.NORMAL
|
||||
|
||||
_match.sendUpdates()
|
||||
return "Match settings have been updated!"
|
||||
|
||||
def mpAbort():
|
||||
_match = glob.matches.matches[getMatchIDFromChannel(chan)]
|
||||
_match.abort()
|
||||
return "Match aborted!"
|
||||
|
||||
def mpKick():
|
||||
if len(message) < 2:
|
||||
raise exceptions.invalidArgumentsException("Wrong syntax: !mp kick <username>")
|
||||
username = message[1].strip()
|
||||
if not username:
|
||||
raise exceptions.invalidArgumentsException("Please provide a username")
|
||||
userID = userUtils.getIDSafe(username)
|
||||
if userID is None:
|
||||
raise exceptions.userNotFoundException("No such user")
|
||||
_match = glob.matches.matches[getMatchIDFromChannel(chan)]
|
||||
slotID = _match.getUserSlotID(userID)
|
||||
if slotID is None:
|
||||
raise exceptions.userNotFoundException("The specified user is not in this match")
|
||||
for i in range(0, 2):
|
||||
_match.toggleSlotLocked(slotID)
|
||||
return "{} has been kicked from the match.".format(username)
|
||||
|
||||
def mpPassword():
|
||||
password = "" if len(message) < 2 or not message[1].strip() else message[1]
|
||||
_match = glob.matches.matches[getMatchIDFromChannel(chan)]
|
||||
_match.changePassword(password)
|
||||
return "Match password has been changed!"
|
||||
|
||||
def mpRandomPassword():
|
||||
password = generalUtils.stringMd5(generalUtils.randomString(32))
|
||||
_match = glob.matches.matches[getMatchIDFromChannel(chan)]
|
||||
_match.changePassword(password)
|
||||
return "Match password has been changed to a random one"
|
||||
|
||||
def mpMods():
|
||||
if len(message) < 2:
|
||||
raise exceptions.invalidArgumentsException("Wrong syntax: !mp <mod1> [<mod2>] ...")
|
||||
_match = glob.matches.matches[getMatchIDFromChannel(chan)]
|
||||
newMods = 0
|
||||
freeMod = False
|
||||
for _mod in message[1:]:
|
||||
if _mod.lower().strip() == "hd":
|
||||
newMods |= mods.HIDDEN
|
||||
elif _mod.lower().strip() == "hr":
|
||||
newMods |= mods.HARDROCK
|
||||
elif _mod.lower().strip() == "dt":
|
||||
newMods |= mods.DOUBLETIME
|
||||
elif _mod.lower().strip() == "fl":
|
||||
newMods |= mods.FLASHLIGHT
|
||||
elif _mod.lower().strip() == "fi":
|
||||
newMods |= mods.FADEIN
|
||||
elif _mod.lower().strip() == "ez":
|
||||
newMods |= mods.EASY
|
||||
if _mod.lower().strip() == "none":
|
||||
newMods = 0
|
||||
|
||||
if _mod.lower().strip() == "freemod":
|
||||
freeMod = True
|
||||
|
||||
_match.matchModMode = matchModModes.FREE_MOD if freeMod else matchModModes.NORMAL
|
||||
_match.resetReady()
|
||||
if _match.matchModMode == matchModModes.FREE_MOD:
|
||||
_match.resetMods()
|
||||
_match.changeMods(newMods)
|
||||
return "Match mods have been updated!"
|
||||
|
||||
def mpTeam():
|
||||
if len(message) < 3:
|
||||
raise exceptions.invalidArgumentsException("Wrong syntax: !mp team <username> <colour>")
|
||||
username = message[1].strip()
|
||||
if not username:
|
||||
raise exceptions.invalidArgumentsException("Please provide a username")
|
||||
colour = message[2].lower().strip()
|
||||
if colour not in ["red", "blue"]:
|
||||
raise exceptions.invalidArgumentsException("Team colour must be red or blue")
|
||||
userID = userUtils.getIDSafe(username)
|
||||
if userID is None:
|
||||
raise exceptions.userNotFoundException("No such user")
|
||||
_match = glob.matches.matches[getMatchIDFromChannel(chan)]
|
||||
_match.changeTeam(userID, matchTeams.BLUE if colour == "blue" else matchTeams.RED)
|
||||
return "{} is now in {} team".format(username, colour)
|
||||
|
||||
def mpSettings():
|
||||
_match = glob.matches.matches[getMatchIDFromChannel(chan)]
|
||||
single = False if len(message) < 2 else message[1].strip().lower() == "single"
|
||||
msg = "PLAYERS IN THIS MATCH "
|
||||
if not single:
|
||||
msg += "(use !mp settings single for a single-line version):"
|
||||
msg += "\n"
|
||||
else:
|
||||
msg += ": "
|
||||
empty = True
|
||||
for slot in _match.slots:
|
||||
if slot.user is None:
|
||||
continue
|
||||
readableStatuses = {
|
||||
slotStatuses.READY: "ready",
|
||||
slotStatuses.NOT_READY: "not ready",
|
||||
slotStatuses.NO_MAP: "no map",
|
||||
slotStatuses.PLAYING: "playing",
|
||||
}
|
||||
if slot.status not in readableStatuses:
|
||||
readableStatus = "???"
|
||||
else:
|
||||
readableStatus = readableStatuses[slot.status]
|
||||
empty = False
|
||||
msg += "* [{team}] <{status}> ~ {username}{mods}{nl}".format(
|
||||
team="red" if slot.team == matchTeams.RED else "blue" if slot.team == matchTeams.BLUE else "!! no team !!",
|
||||
status=readableStatus,
|
||||
username=glob.tokens.tokens[slot.user].username,
|
||||
mods=" (+ {})".format(generalUtils.readableMods(slot.mods)) if slot.mods > 0 else "",
|
||||
nl=" | " if single else "\n"
|
||||
)
|
||||
if empty:
|
||||
msg += "Nobody.\n"
|
||||
msg = msg.rstrip(" | " if single else "\n")
|
||||
return msg
|
||||
|
||||
def mpScoreV():
|
||||
if len(message) < 2 or message[1] not in ("1", "2"):
|
||||
raise exceptions.invalidArgumentsException("Wrong syntax: !mp scorev <1|2>")
|
||||
_match = glob.matches.matches[getMatchIDFromChannel(chan)]
|
||||
_match.matchScoringType = matchScoringTypes.SCORE_V2 if message[1] == "2" else matchScoringTypes.SCORE
|
||||
_match.sendUpdates()
|
||||
return "Match scoring type set to scorev{}".format(message[1])
|
||||
|
||||
def mpHelp():
|
||||
return "Supported subcommands: !mp <{}>".format("|".join(k for k in subcommands.keys()))
|
||||
|
||||
try:
|
||||
subcommands = {
|
||||
"make": mpMake,
|
||||
"close": mpClose,
|
||||
"join": mpJoin,
|
||||
"lock": mpLock,
|
||||
"unlock": mpUnlock,
|
||||
"size": mpSize,
|
||||
"move": mpMove,
|
||||
"host": mpHost,
|
||||
"clearhost": mpClearHost,
|
||||
"start": mpStart,
|
||||
"invite": mpInvite,
|
||||
"map": mpMap,
|
||||
"set": mpSet,
|
||||
"abort": mpAbort,
|
||||
"kick": mpKick,
|
||||
"password": mpPassword,
|
||||
"randompassword": mpRandomPassword,
|
||||
"mods": mpMods,
|
||||
"team": mpTeam,
|
||||
"settings": mpSettings,
|
||||
"scorev": mpScoreV,
|
||||
"help": mpHelp
|
||||
}
|
||||
requestedSubcommand = message[0].lower().strip()
|
||||
if requestedSubcommand not in subcommands:
|
||||
raise exceptions.invalidArgumentsException("Invalid subcommand")
|
||||
return subcommands[requestedSubcommand]()
|
||||
except (exceptions.invalidArgumentsException, exceptions.userNotFoundException, exceptions.invalidUserException) as e:
|
||||
return str(e)
|
||||
except exceptions.wrongChannelException:
|
||||
return "This command only works in multiplayer chat channels"
|
||||
except exceptions.matchNotFoundException:
|
||||
return "Match not found"
|
||||
except:
|
||||
raise
|
||||
|
||||
def switchServer(fro, chan, message):
|
||||
# Get target user ID
|
||||
target = message[0]
|
||||
newServer = message[1].strip()
|
||||
if not newServer:
|
||||
return "Invalid server IP"
|
||||
targetUserID = userUtils.getIDSafe(target)
|
||||
userID = userUtils.getID(fro)
|
||||
|
||||
# Make sure the user exists
|
||||
if not targetUserID:
|
||||
return "{}: user not found".format(target)
|
||||
|
||||
# Connect the user to the end server
|
||||
userToken = glob.tokens.getTokenFromUserID(userID, ignoreIRC=True, _all=False)
|
||||
userToken.enqueue(serverPackets.switchServer(newServer))
|
||||
|
||||
# Disconnect the user from the origin server
|
||||
# userToken.kick()
|
||||
return "{} has been connected to {}".format(target, newServer)
|
||||
|
||||
def rtx(fro, chan, message):
|
||||
target = message[0]
|
||||
message = " ".join(message[1:]).strip()
|
||||
if not message:
|
||||
return "Invalid message"
|
||||
targetUserID = userUtils.getIDSafe(target)
|
||||
if not targetUserID:
|
||||
return "{}: user not found".format(target)
|
||||
userToken = glob.tokens.getTokenFromUserID(targetUserID, ignoreIRC=True, _all=False)
|
||||
userToken.enqueue(serverPackets.rtx(message))
|
||||
return ":ok_hand:"
|
||||
|
||||
def bloodcat(fro, chan, message):
|
||||
try:
|
||||
matchID = getMatchIDFromChannel(chan)
|
||||
except exceptions.wrongChannelException:
|
||||
matchID = None
|
||||
try:
|
||||
spectatorHostUserID = getSpectatorHostUserIDFromChannel(chan)
|
||||
except exceptions.wrongChannelException:
|
||||
spectatorHostUserID = None
|
||||
|
||||
if matchID is not None:
|
||||
if matchID not in glob.matches.matches:
|
||||
return "This match doesn't seem to exist... Or does it...?"
|
||||
beatmapID = glob.matches.matches[matchID].beatmapID
|
||||
else:
|
||||
spectatorHostToken = glob.tokens.getTokenFromUserID(spectatorHostUserID, ignoreIRC=True)
|
||||
if spectatorHostToken is None:
|
||||
return "The spectator host is offline."
|
||||
beatmapID = spectatorHostToken.beatmapID
|
||||
return bloodcatMessage(beatmapID)
|
||||
|
||||
|
||||
"""
|
||||
Commands list
|
||||
|
||||
|
@ -710,10 +1220,10 @@ commands = [
|
|||
"callback": faq
|
||||
}, {
|
||||
"trigger": "!report",
|
||||
"response": "Report command isn't here yet :c"
|
||||
"callback": report
|
||||
}, {
|
||||
"trigger": "!help",
|
||||
"response": "Click (here)[https://ripple.moe/index.php?p=16&id=4] for FokaBot's full command list"
|
||||
"response": "Click (here)[https://ripple.moe/index.php?p=16&id=4] for the full command list"
|
||||
}, #{
|
||||
#"trigger": "!ask",
|
||||
#"syntax": "<question>",
|
||||
|
@ -746,7 +1256,7 @@ commands = [
|
|||
"privileges": privileges.ADMIN_KICK_USERS,
|
||||
"callback": kick
|
||||
}, {
|
||||
"trigger": "!fokabot reconnect",
|
||||
"trigger": "!bot reconnect",
|
||||
"privileges": privileges.ADMIN_MANAGE_SERVERS,
|
||||
"callback": fokabotReconnect
|
||||
}, {
|
||||
|
@ -825,6 +1335,24 @@ commands = [
|
|||
}, {
|
||||
"trigger": "!update",
|
||||
"callback": updateBeatmap
|
||||
}, {
|
||||
"trigger": "!mp",
|
||||
"privileges": privileges.USER_TOURNAMENT_STAFF,
|
||||
"syntax": "<subcommand>",
|
||||
"callback": multiplayer
|
||||
}, {
|
||||
"trigger": "!switchserver",
|
||||
"privileges": privileges.ADMIN_MANAGE_SERVERS,
|
||||
"syntax": "<username> <server_address>",
|
||||
"callback": switchServer
|
||||
}, {
|
||||
"trigger": "!rtx",
|
||||
"privileges": privileges.ADMIN_MANAGE_USERS,
|
||||
"syntax": "<username> <message>",
|
||||
"callback": rtx
|
||||
}, {
|
||||
"trigger": "!bloodcat",
|
||||
"callback": bloodcat
|
||||
}
|
||||
#
|
||||
# "trigger": "!acc",
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
SCORE = 0
|
||||
ACCURACY = 1
|
||||
COMBO = 2
|
||||
SCORE_V2 = 3
|
|
@ -78,5 +78,7 @@ server_userSilenced = 94
|
|||
server_userPresenceBundle = 96
|
||||
client_userPanelRequest = 97
|
||||
client_tournamentMatchInfoRequest = 93
|
||||
server_matchAbort = 106
|
||||
server_switchServer = 107
|
||||
client_tournamentJoinMatchChannel = 108
|
||||
client_tournamentLeaveMatchChannel = 109
|
|
@ -16,12 +16,12 @@ def forceUpdate():
|
|||
|
||||
def loginBanned():
|
||||
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 support@ripple.moe from the email address you've used to sign up.")
|
||||
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"]))
|
||||
return packets
|
||||
|
||||
def loginLocked():
|
||||
packets = packetHelper.buildPacket(packetIDs.server_userID, [[-1, dataTypes.SINT32]])
|
||||
packets += notification("Your account is locked. You can't log in, but your profile and scores are still visible from the website. If you want to unlock your account, send an email to support@ripple.moe from the email address you've used to sign up.")
|
||||
packets += notification("Your account is locked. You can't log in, but your profile and scores are still visible from the website. If you want to unlock your account, send an email to {} from the email address you've used to sign up.".format(glob.conf.extra["pep.py"]["support-email"]))
|
||||
return packets
|
||||
|
||||
def loginError():
|
||||
|
@ -94,12 +94,12 @@ def userPanel(userID, force = False):
|
|||
# Get username color according to rank
|
||||
# Only admins and normal users are currently supported
|
||||
userRank = 0
|
||||
if username == "FokaBot":
|
||||
userRank |= userRanks.MOD
|
||||
elif userUtils.isInPrivilegeGroup(userID, "community manager"):
|
||||
if username == glob.BOT_NAME:
|
||||
userRank |= userRanks.MOD
|
||||
elif userUtils.isInPrivilegeGroup(userID, "developer"):
|
||||
userRank |= userRanks.ADMIN
|
||||
elif userUtils.isInPrivilegeGroup(userID, "chat mod"):
|
||||
userRank |= userRanks.MOD
|
||||
elif (userToken.privileges & privileges.USER_DONOR) > 0:
|
||||
userRank |= userRanks.SUPPORTER
|
||||
else:
|
||||
|
@ -155,11 +155,13 @@ def channelJoinSuccess(userID, chan):
|
|||
return packetHelper.buildPacket(packetIDs.server_channelJoinSuccess, [[chan, dataTypes.STRING]])
|
||||
|
||||
def channelInfo(chan):
|
||||
if chan not in glob.channels.channels:
|
||||
return bytes()
|
||||
channel = glob.channels.channels[chan]
|
||||
return packetHelper.buildPacket(packetIDs.server_channelInfo, [
|
||||
[chan, dataTypes.STRING],
|
||||
[channel.name, dataTypes.STRING],
|
||||
[channel.description, dataTypes.STRING],
|
||||
[len(channel.connectedUsers), dataTypes.UINT16]
|
||||
[len(glob.streams.streams["chat/{}".format(chan)].clients), dataTypes.UINT16]
|
||||
])
|
||||
|
||||
def channelInfoEnd():
|
||||
|
@ -200,17 +202,18 @@ def createMatch(matchID):
|
|||
|
||||
# Get match binary data and build packet
|
||||
match = glob.matches.matches[matchID]
|
||||
return packetHelper.buildPacket(packetIDs.server_newMatch, match.getMatchData())
|
||||
matchData = match.getMatchData(censored=True)
|
||||
return packetHelper.buildPacket(packetIDs.server_newMatch, matchData)
|
||||
|
||||
# TODO: Add match object argument to save some CPU
|
||||
def updateMatch(matchID):
|
||||
def updateMatch(matchID, censored = False):
|
||||
# Make sure the match exists
|
||||
if matchID not in glob.matches.matches:
|
||||
return bytes()
|
||||
|
||||
# Get match binary data and build packet
|
||||
match = glob.matches.matches[matchID]
|
||||
return packetHelper.buildPacket(packetIDs.server_updateMatch, match.getMatchData())
|
||||
return packetHelper.buildPacket(packetIDs.server_updateMatch, match.getMatchData(censored=censored))
|
||||
|
||||
def matchStart(matchID):
|
||||
# Make sure the match exists
|
||||
|
@ -261,10 +264,18 @@ def playerFailed(slotID):
|
|||
def 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 """
|
||||
def notification(message):
|
||||
return packetHelper.buildPacket(packetIDs.server_notification, [[message, dataTypes.STRING]])
|
||||
|
||||
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,6 +1,4 @@
|
|||
from common.constants import actions
|
||||
from common.log import logUtils as log
|
||||
from common.ripple import userUtils
|
||||
from constants import clientPackets
|
||||
from constants import serverPackets
|
||||
from objects import glob
|
||||
|
|
|
@ -15,29 +15,29 @@ def handle(userToken, packetData):
|
|||
matchID = userToken.matchID
|
||||
if matchID not in glob.matches.matches:
|
||||
return
|
||||
match = glob.matches.matches[matchID]
|
||||
|
||||
# Set slot or match mods according to modType
|
||||
if match.matchModMode == matchModModes.FREE_MOD:
|
||||
# Freemod
|
||||
# Host can set global DT/HT
|
||||
if userID == match.hostUserID:
|
||||
# If host has selected DT/HT and Freemod is enabled, set DT/HT as match mod
|
||||
if (packetData["mods"] & mods.DOUBLETIME) > 0:
|
||||
match.changeMods(mods.DOUBLETIME)
|
||||
# Nightcore
|
||||
if (packetData["mods"] & mods.NIGHTCORE) > 0:
|
||||
match.changeMods(match.mods + mods.NIGHTCORE)
|
||||
elif (packetData["mods"] & mods.HALFTIME) > 0:
|
||||
match.changeMods(mods.HALFTIME)
|
||||
else:
|
||||
# No DT/HT, set global mods to 0 (we are in freemod mode)
|
||||
match.changeMods(0)
|
||||
with glob.matches.matches[matchID] as match:
|
||||
if match.matchModMode == matchModModes.FREE_MOD:
|
||||
# Freemod
|
||||
# Host can set global DT/HT
|
||||
if userID == match.hostUserID:
|
||||
# If host has selected DT/HT and Freemod is enabled, set DT/HT as match mod
|
||||
if (packetData["mods"] & mods.DOUBLETIME) > 0:
|
||||
match.changeMods(mods.DOUBLETIME)
|
||||
# Nightcore
|
||||
if (packetData["mods"] & mods.NIGHTCORE) > 0:
|
||||
match.changeMods(match.mods + mods.NIGHTCORE)
|
||||
elif (packetData["mods"] & mods.HALFTIME) > 0:
|
||||
match.changeMods(mods.HALFTIME)
|
||||
else:
|
||||
# No DT/HT, set global mods to 0 (we are in freemod mode)
|
||||
match.changeMods(0)
|
||||
|
||||
# Set slot mods
|
||||
slotID = match.getUserSlotID(userID)
|
||||
if slotID is not None:
|
||||
match.setSlotMods(slotID, packetData["mods"])
|
||||
else:
|
||||
# Not freemod, set match mods
|
||||
match.changeMods(packetData["mods"])
|
||||
# Set slot mods
|
||||
slotID = match.getUserSlotID(userID)
|
||||
if slotID is not None:
|
||||
match.setSlotMods(slotID, packetData["mods"])
|
||||
else:
|
||||
# Not freemod, set match mods
|
||||
match.changeMods(packetData["mods"])
|
||||
|
|
|
@ -10,12 +10,10 @@ def handle(userToken, packetData):
|
|||
if matchID not in glob.matches.matches:
|
||||
return
|
||||
|
||||
# Get our match
|
||||
match = glob.matches.matches[matchID]
|
||||
with glob.matches.matches[matchID] as match:
|
||||
# Host check
|
||||
if userToken.userID != match.hostUserID:
|
||||
return
|
||||
|
||||
# Host check
|
||||
if userToken.userID != match.hostUserID:
|
||||
return
|
||||
|
||||
# Update match password
|
||||
match.changePassword(packetData["matchPassword"])
|
||||
# Update match password
|
||||
match.changePassword(packetData["matchPassword"])
|
||||
|
|
|
@ -21,97 +21,84 @@ def handle(userToken, packetData):
|
|||
if matchID not in glob.matches.matches:
|
||||
return
|
||||
|
||||
# Get match object
|
||||
match = glob.matches.matches[matchID]
|
||||
|
||||
# Host check
|
||||
if userToken.userID != match.hostUserID:
|
||||
return
|
||||
with glob.matches.matches[matchID] as match:
|
||||
if userToken.userID != match.hostUserID:
|
||||
return
|
||||
|
||||
# Some dank memes easter egg
|
||||
memeTitles = [
|
||||
"RWC 2020",
|
||||
"Fokabot is a duck",
|
||||
"Dank memes",
|
||||
"1337ms Ping",
|
||||
"Iscriviti a Xenotoze",
|
||||
"...e i marò?",
|
||||
"Superman dies",
|
||||
"The brace is on fire",
|
||||
"print_foot()",
|
||||
"#FREEZEBARKEZ",
|
||||
"Ripple devs are actually cats",
|
||||
"Thank Mr Shaural",
|
||||
"NEVER GIVE UP",
|
||||
"T I E D W I T H U N I T E D",
|
||||
"HIGHEST HDHR LOBBY OF ALL TIME",
|
||||
"This is gasoline and I set myself on fire",
|
||||
"Everyone is cheating apparently",
|
||||
"Kurwa mac",
|
||||
"TATOE",
|
||||
"This is not your drama landfill.",
|
||||
"I like cheese",
|
||||
"NYO IS NOT A CAT HE IS A DO(N)G",
|
||||
"Datingu startuato"
|
||||
]
|
||||
# Some dank memes easter egg
|
||||
memeTitles = [
|
||||
"RWC 2020",
|
||||
"Fokabot is a duck",
|
||||
"Dank memes",
|
||||
"1337ms Ping",
|
||||
"Iscriviti a Xenotoze",
|
||||
"...e i marò?",
|
||||
"Superman dies",
|
||||
"The brace is on fire",
|
||||
"print_foot()",
|
||||
"#FREEZEBARKEZ",
|
||||
"Ripple devs are actually cats",
|
||||
"Thank Mr Shaural",
|
||||
"NEVER GIVE UP",
|
||||
"T I E D W I T H U N I T E D",
|
||||
"HIGHEST HDHR LOBBY OF ALL TIME",
|
||||
"This is gasoline and I set myself on fire",
|
||||
"Everyone is cheating apparently",
|
||||
"Kurwa mac",
|
||||
"TATOE",
|
||||
"This is not your drama landfill.",
|
||||
"I like cheese",
|
||||
"NYO IS NOT A CAT HE IS A DO(N)G",
|
||||
"Datingu startuato"
|
||||
]
|
||||
|
||||
# Set match name
|
||||
match.matchName = packetData["matchName"] if packetData["matchName"] != "meme" else random.choice(memeTitles)
|
||||
# Set match name
|
||||
match.matchName = packetData["matchName"] if packetData["matchName"] != "meme" else random.choice(memeTitles)
|
||||
|
||||
# Update match settings
|
||||
match.inProgress = packetData["inProgress"]
|
||||
if packetData["matchPassword"] != "":
|
||||
match.matchPassword = generalUtils.stringMd5(packetData["matchPassword"])
|
||||
else:
|
||||
match.matchPassword = ""
|
||||
match.beatmapName = packetData["beatmapName"]
|
||||
match.beatmapID = packetData["beatmapID"]
|
||||
match.hostUserID = packetData["hostUserID"]
|
||||
match.gameMode = packetData["gameMode"]
|
||||
# Update match settings
|
||||
match.inProgress = packetData["inProgress"]
|
||||
if packetData["matchPassword"] != "":
|
||||
match.matchPassword = generalUtils.stringMd5(packetData["matchPassword"])
|
||||
else:
|
||||
match.matchPassword = ""
|
||||
match.beatmapName = packetData["beatmapName"]
|
||||
match.beatmapID = packetData["beatmapID"]
|
||||
match.hostUserID = packetData["hostUserID"]
|
||||
match.gameMode = packetData["gameMode"]
|
||||
|
||||
oldBeatmapMD5 = match.beatmapMD5
|
||||
oldMods = match.mods
|
||||
oldBeatmapMD5 = match.beatmapMD5
|
||||
oldMods = match.mods
|
||||
oldMatchTeamType = match.matchTeamType
|
||||
|
||||
match.mods = packetData["mods"]
|
||||
match.beatmapMD5 = packetData["beatmapMD5"]
|
||||
match.matchScoringType = packetData["scoringType"]
|
||||
match.matchTeamType = packetData["teamType"]
|
||||
match.matchModMode = packetData["freeMods"]
|
||||
match.mods = packetData["mods"]
|
||||
match.beatmapMD5 = packetData["beatmapMD5"]
|
||||
match.matchScoringType = packetData["scoringType"]
|
||||
match.matchTeamType = packetData["teamType"]
|
||||
match.matchModMode = packetData["freeMods"]
|
||||
|
||||
# Reset ready if needed
|
||||
if oldMods != match.mods or oldBeatmapMD5 != match.beatmapMD5:
|
||||
for i in range(0,16):
|
||||
if match.slots[i].status == slotStatuses.READY:
|
||||
match.slots[i].status = slotStatuses.NOT_READY
|
||||
# Reset ready if needed
|
||||
if oldMods != match.mods or oldBeatmapMD5 != match.beatmapMD5:
|
||||
match.resetReady()
|
||||
|
||||
# Reset mods if needed
|
||||
if match.matchModMode == matchModModes.NORMAL:
|
||||
# Reset slot mods if not freeMods
|
||||
for i in range(0,16):
|
||||
match.slots[i].mods = 0
|
||||
else:
|
||||
# Reset match mods if freemod
|
||||
match.mods = 0
|
||||
# Reset mods if needed
|
||||
if match.matchModMode == matchModModes.NORMAL:
|
||||
# Reset slot mods if not freeMods
|
||||
match.resetMods()
|
||||
else:
|
||||
# Reset match mods if freemod
|
||||
match.mods = 0
|
||||
|
||||
# Set/reset teams
|
||||
if match.matchTeamType == matchTeamTypes.TEAM_VS or match.matchTeamType == matchTeamTypes.TAG_TEAM_VS:
|
||||
# Set teams
|
||||
c=0
|
||||
for i in range(0,16):
|
||||
if match.slots[i].team == matchTeams.NO_TEAM:
|
||||
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.NO_TEAM
|
||||
# Initialize teams if team type changed
|
||||
if match.matchTeamType != oldMatchTeamType:
|
||||
match.initializeTeams()
|
||||
|
||||
# Force no freemods if tag coop
|
||||
if match.matchTeamType == matchTeamTypes.TAG_COOP or match.matchTeamType == matchTeamTypes.TAG_TEAM_VS:
|
||||
match.matchModMode = matchModModes.NORMAL
|
||||
# Force no freemods if tag coop
|
||||
if match.matchTeamType == matchTeamTypes.TAG_COOP or match.matchTeamType == matchTeamTypes.TAG_TEAM_VS:
|
||||
match.matchModMode = matchModModes.NORMAL
|
||||
|
||||
# Send updated settings
|
||||
match.sendUpdates()
|
||||
# Send updated settings
|
||||
match.sendUpdates()
|
||||
|
||||
# Console output
|
||||
log.info("MPROOM{}: Updated room settings".format(match.matchID))
|
||||
# Console output
|
||||
log.info("MPROOM{}: Updated room settings".format(match.matchID))
|
||||
|
|
|
@ -8,8 +8,6 @@ def handle(userToken, packetData):
|
|||
# Read packet data
|
||||
packetData = clientPackets.changeSlot(packetData)
|
||||
|
||||
# Get match
|
||||
match = glob.matches.matches[userToken.matchID]
|
||||
|
||||
# Change slot
|
||||
match.userChangeSlot(userID, packetData["slotID"])
|
||||
with glob.matches.matches[userToken.matchID] as match:
|
||||
# Change slot
|
||||
match.userChangeSlot(userID, packetData["slotID"])
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
from common.log import logUtils as log
|
||||
from constants import clientPackets
|
||||
from constants import clientPackets, serverPackets
|
||||
from constants import exceptions
|
||||
from constants import serverPackets
|
||||
from objects import glob
|
||||
|
||||
|
||||
|
@ -13,26 +12,32 @@ def handle(userToken, packetData):
|
|||
# Read packet data
|
||||
packetData = clientPackets.createMatch(packetData)
|
||||
|
||||
# Make sure the name is valid
|
||||
matchName = packetData["matchName"].strip()
|
||||
if not matchName:
|
||||
raise exceptions.matchCreateError()
|
||||
|
||||
# Create a match object
|
||||
# TODO: Player number check
|
||||
matchID = glob.matches.createMatch(packetData["matchName"], packetData["matchPassword"], packetData["beatmapID"], packetData["beatmapName"], packetData["beatmapMD5"], packetData["gameMode"], userID)
|
||||
# TODO: Player number check (Dirty hack below)
|
||||
matchID = glob.matches.createMatch(matchName, packetData["matchPassword"].strip(), packetData["beatmapID"], packetData["beatmapName"], packetData["beatmapMD5"], packetData["gameMode"], userID)
|
||||
|
||||
# Make sure the match has been created
|
||||
if matchID not in glob.matches.matches:
|
||||
raise exceptions.matchCreateError
|
||||
raise exceptions.matchCreateError()
|
||||
|
||||
# Get match object
|
||||
match = glob.matches.matches[matchID]
|
||||
with glob.matches.matches[matchID] as match:
|
||||
# Join that match
|
||||
userToken.joinMatch(matchID)
|
||||
|
||||
# Join that match
|
||||
userToken.joinMatch(matchID)
|
||||
# Disable slots (Dirty)
|
||||
for i in range(0,16):
|
||||
if match.slots[i].status is not 4:
|
||||
match.slots[i].status = packetData["slot{}Status".format(i)]
|
||||
|
||||
# Give host to match creator
|
||||
match.setHost(userID)
|
||||
match.sendUpdates()
|
||||
match.changePassword(packetData["matchPassword"])
|
||||
|
||||
# Console output
|
||||
log.info("MPROOM{}: Room created!".format(matchID))
|
||||
# Give host to match creator
|
||||
match.setHost(userID)
|
||||
match.sendUpdates()
|
||||
match.changePassword(packetData["matchPassword"])
|
||||
except exceptions.matchCreateError:
|
||||
log.error("Error while creating match!")
|
||||
userToken.enqueue(serverPackets.matchJoinFail())
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
from common import generalUtils
|
||||
from common.log import logUtils as log
|
||||
from constants import clientPackets
|
||||
from constants import exceptions
|
||||
|
@ -18,21 +17,17 @@ def handle(userToken, packetData):
|
|||
if matchID not in glob.matches.matches:
|
||||
return
|
||||
|
||||
# Match exists, get object
|
||||
match = glob.matches.matches[matchID]
|
||||
|
||||
# Hash password if needed
|
||||
#if password != "":
|
||||
# if password != "":
|
||||
# password = generalUtils.stringMd5(password)
|
||||
|
||||
# Check password
|
||||
# TODO: Admins can enter every match
|
||||
if match.matchPassword != "":
|
||||
if match.matchPassword != password:
|
||||
raise exceptions.matchWrongPasswordException
|
||||
with glob.matches.matches[matchID] as match:
|
||||
if match.matchPassword != "" and match.matchPassword != password:
|
||||
raise exceptions.matchWrongPasswordException()
|
||||
|
||||
# Password is correct, join match
|
||||
userToken.joinMatch(matchID)
|
||||
# Password is correct, join match
|
||||
userToken.joinMatch(matchID)
|
||||
except exceptions.matchWrongPasswordException:
|
||||
userToken.enqueue(serverPackets.matchJoinFail())
|
||||
log.warning("{} has tried to join a mp room, but he typed the wrong password".format(userToken.username))
|
|
@ -15,6 +15,7 @@ from objects import glob
|
|||
|
||||
def handle(tornadoRequest):
|
||||
# Data to return
|
||||
responseToken = None
|
||||
responseTokenString = "ayy"
|
||||
responseData = bytes()
|
||||
|
||||
|
@ -29,9 +30,6 @@ def handle(tornadoRequest):
|
|||
# 2:-3 thing is because requestData has some escape stuff that we don't need
|
||||
loginData = str(tornadoRequest.request.body)[2:-3].split("\\n")
|
||||
try:
|
||||
# If true, print error to console
|
||||
err = False
|
||||
|
||||
# Make sure loginData is valid
|
||||
if len(loginData) < 3:
|
||||
raise exceptions.invalidArgumentsException()
|
||||
|
@ -63,9 +61,9 @@ def handle(tornadoRequest):
|
|||
|
||||
# Make sure we are not banned or locked
|
||||
priv = userUtils.getPrivileges(userID)
|
||||
if userUtils.isBanned(userID) == True and priv & privileges.USER_PENDING_VERIFICATION == 0:
|
||||
if userUtils.isBanned(userID) and priv & privileges.USER_PENDING_VERIFICATION == 0:
|
||||
raise exceptions.loginBannedException()
|
||||
if userUtils.isLocked(userID) == True and priv & privileges.USER_PENDING_VERIFICATION == 0:
|
||||
if userUtils.isLocked(userID) and priv & privileges.USER_PENDING_VERIFICATION == 0:
|
||||
raise exceptions.loginLockedException()
|
||||
|
||||
# 2FA check
|
||||
|
@ -77,7 +75,7 @@ def handle(tornadoRequest):
|
|||
|
||||
# Verify this user (if pending activation)
|
||||
firstLogin = False
|
||||
if priv & privileges.USER_PENDING_VERIFICATION > 0 or userUtils.hasVerifiedHardware(userID) == False:
|
||||
if priv & privileges.USER_PENDING_VERIFICATION > 0 or not userUtils.hasVerifiedHardware(userID):
|
||||
if userUtils.verifyUser(userID, clientData):
|
||||
# Valid account
|
||||
log.info("Account {} verified successfully!".format(userID))
|
||||
|
@ -120,6 +118,9 @@ def handle(tornadoRequest):
|
|||
expireIn = "{} days".format(expireDays) if expireDays > 1 else "less than 24 hours"
|
||||
responseToken.enqueue(serverPackets.notification("Your donor tag expires in {}! When your donor tag expires, you won't have any of the donor privileges, like yellow username, custom badge and discord custom role and username color! If you wish to keep supporting Ripple and you don't want to lose your donor privileges, you can donate again by clicking on 'Support us' on Ripple's website.".format(expireIn)))
|
||||
|
||||
# Deprecate telegram 2fa and send alert
|
||||
if userUtils.deprecateTelegram2Fa(userID):
|
||||
responseToken.enqueue(serverPackets.notification("As stated on our blog, Telegram 2FA has been deprecated on 29th June 2018. Telegram 2FA has just been disabled from your account. If you want to keep your account secure with 2FA, please enable TOTP-based 2FA from our website https://ripple.moe. Thank you for your patience."))
|
||||
|
||||
# Set silence end UNIX time in token
|
||||
responseToken.silenceEndTime = userUtils.getSilenceEnd(userID)
|
||||
|
@ -175,7 +176,7 @@ def handle(tornadoRequest):
|
|||
|
||||
# Output channels info
|
||||
for key, value in glob.channels.channels.items():
|
||||
if value.publicRead == True and value.hidden == False:
|
||||
if value.publicRead and not value.hidden:
|
||||
responseToken.enqueue(serverPackets.channelInfo(key))
|
||||
|
||||
# Send friends list
|
||||
|
@ -185,8 +186,11 @@ def handle(tornadoRequest):
|
|||
if glob.banchoConf.config["menuIcon"] != "":
|
||||
responseToken.enqueue(serverPackets.mainMenuIcon(glob.banchoConf.config["menuIcon"]))
|
||||
|
||||
# Send online users IDs array
|
||||
responseToken.enqueue(serverPackets.onlineUsers())
|
||||
# Send online users' panels
|
||||
with glob.tokens:
|
||||
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
|
||||
if glob.localize:
|
||||
|
@ -220,25 +224,23 @@ def handle(tornadoRequest):
|
|||
except exceptions.loginFailedException:
|
||||
# Login failed error packet
|
||||
# (we don't use enqueue because we don't have a token since login has failed)
|
||||
err = True
|
||||
responseData += serverPackets.loginFailed()
|
||||
except exceptions.invalidArgumentsException:
|
||||
# Invalid POST data
|
||||
# (we don't use enqueue because we don't have a token since login has failed)
|
||||
err = True
|
||||
responseData += serverPackets.loginFailed()
|
||||
responseData += serverPackets.notification("I see what you're doing...")
|
||||
except exceptions.loginBannedException:
|
||||
# Login banned error packet
|
||||
err = True
|
||||
responseData += serverPackets.loginBanned()
|
||||
except exceptions.loginLockedException:
|
||||
# Login banned error packet
|
||||
err = True
|
||||
responseData += serverPackets.loginLocked()
|
||||
except exceptions.banchoMaintenanceException:
|
||||
# Bancho is in maintenance mode
|
||||
responseData = responseToken.queue
|
||||
responseData = bytes()
|
||||
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.loginFailed()
|
||||
except exceptions.banchoRestartingException:
|
||||
|
@ -251,7 +253,6 @@ def handle(tornadoRequest):
|
|||
except exceptions.haxException:
|
||||
# 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)
|
||||
err = True
|
||||
responseData += serverPackets.forceUpdate()
|
||||
responseData += serverPackets.notification("Hory shitto, your client is TOO old! Nice prehistory! Please turn update it from the settings!")
|
||||
except:
|
||||
|
@ -259,10 +260,7 @@ def handle(tornadoRequest):
|
|||
finally:
|
||||
# Console and discord log
|
||||
if len(loginData) < 3:
|
||||
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")
|
||||
log.info("Invalid bancho login request from **{}** (insufficient POST data)".format(requestIP), "bunker")
|
||||
|
||||
# Return token string and data
|
||||
return responseTokenString, responseData
|
||||
return responseTokenString, responseData
|
||||
|
|
|
@ -17,7 +17,7 @@ def handle(userToken, _=None, deleteToken=True):
|
|||
# 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
|
||||
# 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
|
||||
userToken.stopSpectating()
|
||||
|
||||
|
@ -35,7 +35,7 @@ def handle(userToken, _=None, deleteToken=True):
|
|||
glob.streams.broadcast("main", serverPackets.userLogout(userID))
|
||||
|
||||
# Disconnect from IRC if needed
|
||||
if userToken.irc == True and glob.irc == True:
|
||||
if userToken.irc and glob.irc:
|
||||
glob.ircServer.forceDisconnection(userToken.username)
|
||||
|
||||
# Delete token
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from objects import glob
|
||||
|
||||
def handle(userToken, packetData, has):
|
||||
def handle(userToken, _, has):
|
||||
# Get usertoken data
|
||||
userID = userToken.userID
|
||||
|
||||
|
@ -15,8 +15,6 @@ def handle(userToken, packetData, has):
|
|||
if matchID not in glob.matches.matches:
|
||||
return
|
||||
|
||||
# The match exists, get object
|
||||
match = glob.matches.matches[matchID]
|
||||
|
||||
# Set has beatmap/no beatmap
|
||||
match.userHasBeatmap(userID, has)
|
||||
with glob.matches.matches[matchID] as match:
|
||||
match.userHasBeatmap(userID, has)
|
||||
|
|
|
@ -15,8 +15,6 @@ def handle(userToken, _):
|
|||
if matchID not in glob.matches.matches:
|
||||
return
|
||||
|
||||
# Get match object
|
||||
match = glob.matches.matches[matchID]
|
||||
|
||||
# Change team
|
||||
match.changeTeam(userID)
|
||||
with glob.matches.matches[matchID] as match:
|
||||
match.changeTeam(userID)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from objects import glob
|
||||
|
||||
def handle(userToken, packetData):
|
||||
def handle(userToken, _):
|
||||
# Get usertoken data
|
||||
userID = userToken.userID
|
||||
|
||||
|
@ -15,8 +15,6 @@ def handle(userToken, packetData):
|
|||
if matchID not in glob.matches.matches:
|
||||
return
|
||||
|
||||
# The match exists, get object
|
||||
match = glob.matches.matches[matchID]
|
||||
|
||||
# Set our match complete
|
||||
match.playerCompleted(userID)
|
||||
with glob.matches.matches[matchID] as match:
|
||||
match.playerCompleted(userID)
|
||||
|
|
|
@ -15,8 +15,6 @@ def handle(userToken, _):
|
|||
if matchID not in glob.matches.matches:
|
||||
return
|
||||
|
||||
# Match exists, get object
|
||||
match = glob.matches.matches[matchID]
|
||||
|
||||
# Fail user
|
||||
match.playerFailed(userID)
|
||||
with glob.matches.matches[matchID] as match:
|
||||
match.playerFailed(userID)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from objects import glob
|
||||
from constants import slotStatuses
|
||||
from constants import serverPackets
|
||||
from constants import serverPackets, clientPackets
|
||||
|
||||
def handle(userToken, packetData):
|
||||
# Get usertoken data
|
||||
|
@ -17,11 +16,16 @@ def handle(userToken, packetData):
|
|||
if matchID not in glob.matches.matches:
|
||||
return
|
||||
|
||||
# The match exists, get object
|
||||
match = glob.matches.matches[matchID]
|
||||
# Parse the data
|
||||
data = clientPackets.matchFrames(packetData)
|
||||
|
||||
# Change slot id in packetData
|
||||
slotID = match.getUserSlotID(userID)
|
||||
with glob.matches.matches[matchID] as match:
|
||||
# Change slot id in packetData
|
||||
slotID = match.getUserSlotID(userID)
|
||||
|
||||
# Enqueue frames to who's playing
|
||||
glob.streams.broadcast(match.playingStreamName, serverPackets.matchFrames(slotID, packetData))
|
||||
# Update the score
|
||||
match.updateScore(slotID, data["totalScore"])
|
||||
match.updateHP(slotID, data["currentHp"])
|
||||
|
||||
# Enqueue frames to who's playing
|
||||
glob.streams.broadcast(match.playingStreamName, serverPackets.matchFrames(slotID, packetData))
|
|
@ -17,8 +17,6 @@ def handle(userToken, packetData):
|
|||
if matchID not in glob.matches.matches:
|
||||
return
|
||||
|
||||
# Get match object
|
||||
match = glob.matches.matches[matchID]
|
||||
|
||||
# Send invite
|
||||
match.invite(userID, packetData["userID"])
|
||||
with glob.matches.matches[matchID] as match:
|
||||
match.invite(userID, packetData["userID"])
|
||||
|
|
|
@ -12,16 +12,16 @@ def handle(userToken, packetData):
|
|||
matchID = userToken.matchID
|
||||
if matchID not in glob.matches.matches:
|
||||
return
|
||||
match = glob.matches.matches[matchID]
|
||||
|
||||
# Host check
|
||||
if userID != match.hostUserID:
|
||||
return
|
||||
with glob.matches.matches[matchID] as match:
|
||||
# Host check
|
||||
if userID != match.hostUserID:
|
||||
return
|
||||
|
||||
# Make sure we aren't locking our slot
|
||||
ourSlot = match.getUserSlotID(userID)
|
||||
if packetData["slotID"] == ourSlot:
|
||||
return
|
||||
# Make sure we aren't locking our slot
|
||||
ourSlot = match.getUserSlotID(userID)
|
||||
if packetData["slotID"] == ourSlot:
|
||||
return
|
||||
|
||||
# Lock/Unlock slot
|
||||
match.toggleSlotLock(packetData["slotID"])
|
||||
# Lock/Unlock slot
|
||||
match.toggleSlotLocked(packetData["slotID"])
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from objects import glob
|
||||
|
||||
def handle(userToken, packetData):
|
||||
def handle(userToken, _):
|
||||
# Get userToken data
|
||||
userID = userToken.userID
|
||||
|
||||
|
@ -15,8 +15,6 @@ def handle(userToken, packetData):
|
|||
if matchID not in glob.matches.matches:
|
||||
return
|
||||
|
||||
# The match exists, get object
|
||||
match = glob.matches.matches[matchID]
|
||||
|
||||
# Set our load status
|
||||
match.playerLoaded(userID)
|
||||
with glob.matches.matches[matchID] as match:
|
||||
match.playerLoaded(userID)
|
||||
|
|
|
@ -8,9 +8,14 @@ def handle(userToken, _):
|
|||
matchID = userToken.matchID
|
||||
if matchID not in glob.matches.matches:
|
||||
return
|
||||
match = glob.matches.matches[matchID]
|
||||
|
||||
# Get our slotID and change ready status
|
||||
slotID = match.getUserSlotID(userID)
|
||||
if slotID is not None:
|
||||
match.toggleSlotReady(slotID)
|
||||
with glob.matches.matches[matchID] as match:
|
||||
# Get our slotID and change ready status
|
||||
slotID = match.getUserSlotID(userID)
|
||||
if slotID is not None:
|
||||
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
|
||||
|
||||
def handle(userToken, packetData):
|
||||
def handle(userToken, _):
|
||||
# Get userToken data
|
||||
userID = userToken.userID
|
||||
|
||||
|
@ -15,8 +15,6 @@ def handle(userToken, packetData):
|
|||
if matchID not in glob.matches.matches:
|
||||
return
|
||||
|
||||
# The match exists, get object
|
||||
match = glob.matches.matches[matchID]
|
||||
|
||||
# Skip
|
||||
match.playerSkip(userID)
|
||||
with glob.matches.matches[matchID] as match:
|
||||
match.playerSkip(userID)
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
from objects import glob
|
||||
from constants import slotStatuses
|
||||
from constants import serverPackets
|
||||
|
||||
def handle(userToken, _):
|
||||
|
||||
|
@ -15,11 +13,9 @@ def handle(userToken, _):
|
|||
if matchID not in glob.matches.matches:
|
||||
return
|
||||
|
||||
# The match exists, get object
|
||||
match = glob.matches.matches[matchID]
|
||||
with glob.matches.matches[matchID] as match:
|
||||
# Host check
|
||||
if userToken.userID != match.hostUserID:
|
||||
return
|
||||
|
||||
# Host check
|
||||
if userToken.userID != match.hostUserID:
|
||||
return
|
||||
|
||||
match.start()
|
||||
match.start()
|
||||
|
|
|
@ -16,12 +16,10 @@ def handle(userToken, packetData):
|
|||
if matchID not in glob.matches.matches:
|
||||
return
|
||||
|
||||
# Match exists, get object
|
||||
match = glob.matches.matches[matchID]
|
||||
|
||||
# Host check
|
||||
if userToken.userID != match.hostUserID:
|
||||
return
|
||||
with glob.matches.matches[matchID] as match:
|
||||
if userToken.userID != match.hostUserID:
|
||||
return
|
||||
|
||||
# Transfer host
|
||||
match.transferHost(packetData["slotID"])
|
||||
# Transfer host
|
||||
match.transferHost(packetData["slotID"])
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
from common.log import logUtils as log
|
||||
from helpers import chatHelper as chat
|
||||
from objects import glob
|
||||
|
||||
|
||||
def handle(userToken, _):
|
||||
# Get usertoken data
|
||||
userID = userToken.userID
|
||||
username = userToken.username
|
||||
|
||||
# Remove user from users in lobby
|
||||
userToken.leaveStream("lobby")
|
||||
|
||||
# Part lobby channel
|
||||
# Done automatically by the client
|
||||
chat.partChannel(channel="#lobby", token=userToken, kick=True)
|
||||
|
||||
# Console output
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from common.log import logUtils as log
|
||||
from constants import clientPackets
|
||||
from constants import serverPackets
|
||||
from objects import glob
|
||||
|
||||
|
||||
def handle(userToken, packetData):
|
||||
|
@ -18,5 +19,5 @@ def handle(userToken, packetData):
|
|||
fokaMessage = "Your away message has been reset"
|
||||
else:
|
||||
fokaMessage = "Your away message is now: {}".format(packetData["awayMessage"])
|
||||
userToken.enqueue(serverPackets.sendMessage("FokaBot", username, fokaMessage))
|
||||
userToken.enqueue(serverPackets.sendMessage(glob.BOT_NAME, username, fokaMessage))
|
||||
log.info("{} has changed their away message to: {}".format(username, packetData["awayMessage"]))
|
||||
|
|
|
@ -1,31 +1,15 @@
|
|||
from objects import glob
|
||||
from constants import serverPackets
|
||||
from constants import exceptions
|
||||
from common.log import logUtils as log
|
||||
|
||||
def handle(userToken, packetData):
|
||||
# get token data
|
||||
userID = userToken.userID
|
||||
|
||||
# Send spectator frames to every spectator
|
||||
glob.streams.broadcast("spect/{}".format(userID), serverPackets.spectatorFrames(packetData[7:]))
|
||||
'''for i in userToken.spectators:
|
||||
# Send to every user but host
|
||||
if i != userID:
|
||||
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))'''
|
||||
streamName = "spect/{}".format(userID)
|
||||
glob.streams.broadcast(streamName, serverPackets.spectatorFrames(packetData[7:]))
|
||||
log.debug("Broadcasting {}'s frames to {} clients".format(
|
||||
userID,
|
||||
len(glob.streams.streams[streamName].clients))
|
||||
)
|
|
@ -8,6 +8,11 @@ def handle(userToken, packetData):
|
|||
# Start spectating packet
|
||||
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
|
||||
targetToken = glob.tokens.getTokenFromUserID(packetData["userID"])
|
||||
if targetToken is None:
|
||||
|
|
|
@ -5,7 +5,7 @@ from helpers import chatHelper as chat
|
|||
def handle(userToken, packetData):
|
||||
packetData = clientPackets.tournamentJoinMatchChannel(packetData)
|
||||
matchID = packetData["matchID"]
|
||||
if matchID not in glob.matches.matches:
|
||||
if matchID not in glob.matches.matches or not userToken.tournament:
|
||||
return
|
||||
userToken.matchID = matchID
|
||||
chat.joinChannel(token=userToken, channel="#multi_{}".format(matchID))
|
||||
chat.joinChannel(token=userToken, channel="#multi_{}".format(matchID), force=True)
|
|
@ -5,7 +5,7 @@ from helpers import chatHelper as chat
|
|||
def handle(userToken, packetData):
|
||||
packetData = clientPackets.tournamentLeaveMatchChannel(packetData)
|
||||
matchID = packetData["matchID"]
|
||||
if matchID not in glob.matches.matches:
|
||||
if matchID not in glob.matches.matches or not userToken.tournament:
|
||||
return
|
||||
chat.partChannel(token=userToken, channel="#multi_{}".format(matchID))
|
||||
chat.partChannel(token=userToken, channel="#multi_{}".format(matchID), force=True)
|
||||
userToken.matchID = 0
|
|
@ -1,10 +1,10 @@
|
|||
from constants import clientPackets
|
||||
from constants import serverPackets
|
||||
from objects import glob
|
||||
|
||||
def handle(userToken, packetData):
|
||||
packetData = clientPackets.tournamentMatchInfoRequest(packetData)
|
||||
matchID = packetData["matchID"]
|
||||
if matchID not in glob.matches.matches:
|
||||
if matchID not in glob.matches.matches or not userToken.tournament:
|
||||
return
|
||||
userToken.enqueue(glob.matches.matches[matchID].matchDataCache)
|
||||
with glob.matches.matches[matchID] as m:
|
||||
userToken.enqueue(m.matchDataCache)
|
39
filters.txt
39
filters.txt
|
@ -1,16 +1,23 @@
|
|||
fuck=firetruck
|
||||
shit=shish
|
||||
ass=peach
|
||||
asses=peaches
|
||||
bitch=fine lady
|
||||
bitches=fine ladies
|
||||
asshole=donkey
|
||||
ass hole=donkey
|
||||
cock=chicken
|
||||
cocks=chickens
|
||||
dick=eggplant
|
||||
dicks=eggplants
|
||||
boobs=bob
|
||||
tits=teeth
|
||||
cum=yogurt
|
||||
cunt=count
|
||||
fuck=firetruck
|
||||
shit=shish
|
||||
ass=peach
|
||||
asses=peaches
|
||||
bitch=fine lady
|
||||
bitches=fine ladies
|
||||
asshole=donkey
|
||||
ass hole=donkey
|
||||
cock=chicken
|
||||
cocks=chickens
|
||||
dick=eggplant
|
||||
dicks=eggplants
|
||||
boobs=bob
|
||||
tits=teeth
|
||||
cum=yogurt
|
||||
cunt=count
|
||||
nigger=flowers
|
||||
ngger=flowers
|
||||
niggers=flowers
|
||||
weed=grass
|
||||
AQN=meme
|
||||
theaquila=meme
|
||||
aquila=meme
|
|
@ -1,6 +1,9 @@
|
|||
import json
|
||||
|
||||
from common.log import logUtils as log
|
||||
import tornado.web
|
||||
import tornado.gen
|
||||
|
||||
from common.sentry import sentry
|
||||
from common.web import requestsManager
|
||||
from constants import exceptions
|
||||
from helpers import chatHelper
|
||||
|
@ -8,6 +11,9 @@ from objects import glob
|
|||
|
||||
|
||||
class handler(requestsManager.asyncRequestHandler):
|
||||
@tornado.web.asynchronous
|
||||
@tornado.gen.engine
|
||||
@sentry.captureTornado
|
||||
def asyncGet(self):
|
||||
statusCode = 400
|
||||
data = {"message": "unknown error"}
|
||||
|
@ -21,7 +27,11 @@ class handler(requestsManager.asyncRequestHandler):
|
|||
if key is None or key != glob.conf.config["server"]["cikey"]:
|
||||
raise exceptions.invalidArgumentsException()
|
||||
|
||||
chatHelper.sendMessage("FokaBot", self.get_argument("to"), self.get_argument("msg"))
|
||||
chatHelper.sendMessage(
|
||||
glob.BOT_NAME,
|
||||
self.get_argument("to").encode().decode("ASCII", "ignore"),
|
||||
self.get_argument("msg").encode().decode("ASCII", "ignore")
|
||||
)
|
||||
|
||||
# Status code and message
|
||||
statusCode = 200
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
import json
|
||||
|
||||
import tornado.web
|
||||
import tornado.gen
|
||||
|
||||
from common.sentry import sentry
|
||||
from common.ripple import userUtils
|
||||
from common.web import requestsManager
|
||||
from constants import exceptions
|
||||
from objects import glob
|
||||
|
||||
|
||||
class handler(requestsManager.asyncRequestHandler):
|
||||
@tornado.web.asynchronous
|
||||
@tornado.gen.engine
|
||||
@sentry.captureTornado
|
||||
def asyncGet(self):
|
||||
statusCode = 400
|
||||
data = {"message": "unknown error"}
|
||||
|
@ -18,7 +26,8 @@ class handler(requestsManager.asyncRequestHandler):
|
|||
username = None
|
||||
userID = None
|
||||
if "u" in self.request.arguments:
|
||||
username = self.get_argument("u").lower().replace(" ", "_")
|
||||
#username = self.get_argument("u").lower().replace(" ", "_")
|
||||
username = userUtils.safeUsername(self.get_argument("u"))
|
||||
else:
|
||||
try:
|
||||
userID = int(self.get_argument("id"))
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
import json
|
||||
|
||||
import tornado.web
|
||||
import tornado.gen
|
||||
|
||||
from common.sentry import sentry
|
||||
from common.web import requestsManager
|
||||
from objects import glob
|
||||
|
||||
|
||||
class handler(requestsManager.asyncRequestHandler):
|
||||
@tornado.web.asynchronous
|
||||
@tornado.gen.engine
|
||||
@sentry.captureTornado
|
||||
def asyncGet(self):
|
||||
statusCode = 400
|
||||
data = {"message": "unknown error"}
|
||||
|
|
|
@ -1,16 +1,23 @@
|
|||
import json
|
||||
|
||||
import tornado.web
|
||||
import tornado.gen
|
||||
|
||||
from common.sentry import sentry
|
||||
from common.web import requestsManager
|
||||
from objects import glob
|
||||
|
||||
|
||||
class handler(requestsManager.asyncRequestHandler):
|
||||
@tornado.web.asynchronous
|
||||
@tornado.gen.engine
|
||||
@sentry.captureTornado
|
||||
def asyncGet(self):
|
||||
statusCode = 400
|
||||
data = {"message": "unknown error"}
|
||||
try:
|
||||
# Get online users count
|
||||
data["result"] = -1 if glob.restarting == True else 1
|
||||
data["result"] = -1 if glob.restarting else 1
|
||||
|
||||
# Status code and message
|
||||
statusCode = 200
|
||||
|
|
|
@ -1,11 +1,18 @@
|
|||
import json
|
||||
|
||||
import tornado.web
|
||||
import tornado.gen
|
||||
|
||||
from common.sentry import sentry
|
||||
from common.web import requestsManager
|
||||
from constants import exceptions
|
||||
from objects import glob
|
||||
|
||||
|
||||
class handler(requestsManager.asyncRequestHandler):
|
||||
@tornado.web.asynchronous
|
||||
@tornado.gen.engine
|
||||
@sentry.captureTornado
|
||||
def asyncGet(self):
|
||||
statusCode = 400
|
||||
data = {"message": "unknown error"}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import json
|
||||
|
||||
import tornado.web
|
||||
import tornado.gen
|
||||
|
||||
from common.sentry import sentry
|
||||
from common.log import logUtils as log
|
||||
from common.web import requestsManager
|
||||
from constants import exceptions
|
||||
|
@ -8,6 +12,9 @@ from objects import glob
|
|||
|
||||
|
||||
class handler(requestsManager.asyncRequestHandler):
|
||||
@tornado.web.asynchronous
|
||||
@tornado.gen.engine
|
||||
@sentry.captureTornado
|
||||
def asyncGet(self):
|
||||
statusCode = 400
|
||||
data = {"message": "unknown error"}
|
||||
|
|
|
@ -56,199 +56,194 @@ from events import tournamentJoinMatchChannelEvent
|
|||
from events import tournamentLeaveMatchChannelEvent
|
||||
from helpers import packetHelper
|
||||
from objects import glob
|
||||
from common.sentry import sentry
|
||||
|
||||
|
||||
class handler(SentryMixin, requestsManager.asyncRequestHandler):
|
||||
class handler(requestsManager.asyncRequestHandler):
|
||||
@tornado.web.asynchronous
|
||||
@tornado.gen.engine
|
||||
@sentry.captureTornado
|
||||
def asyncPost(self):
|
||||
try:
|
||||
# Track time if needed
|
||||
if glob.outputRequestTime:
|
||||
# Start time
|
||||
st = datetime.datetime.now()
|
||||
# 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
|
||||
# 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()
|
||||
# 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
|
||||
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()
|
||||
# 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()
|
||||
# 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:]
|
||||
# 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)]
|
||||
# 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)))
|
||||
# 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
|
||||
# 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),
|
||||
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_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_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_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),
|
||||
}
|
||||
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,
|
||||
]
|
||||
# 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))
|
||||
# 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("Unknown packet id from {} ({})".format(requestTokenString, packetID))
|
||||
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
|
||||
# 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()
|
||||
# Delete token if kicked
|
||||
if userToken.kicked:
|
||||
glob.tokens.deleteToken(userToken)
|
||||
# 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()
|
||||
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))
|
||||
# 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"])))
|
||||
# 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)
|
||||
# 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()
|
||||
# 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%}</style></head><body><pre>"
|
||||
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>"
|
||||
|
@ -267,5 +262,5 @@ class handler(SentryMixin, requestsManager.asyncRequestHandler):
|
|||
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>"
|
||||
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,7 +8,7 @@ from objects import fokabot
|
|||
from objects import glob
|
||||
|
||||
|
||||
def joinChannel(userID = 0, channel = "", token = None, toIRC = True):
|
||||
def joinChannel(userID = 0, channel = "", token = None, toIRC = True, force=False):
|
||||
"""
|
||||
Join a channel
|
||||
|
||||
|
@ -16,6 +16,7 @@ def joinChannel(userID = 0, channel = "", token = None, toIRC = True):
|
|||
:param token: user token object of user that joins the channel. Optional. userID can be used instead.
|
||||
:param channel: channel name
|
||||
:param toIRC: if True, send this channel join event to IRC. Must be true if joining from bancho. Default: True
|
||||
:param force: whether to allow game clients to join #spect_ and #multi_ channels
|
||||
:return: 0 if joined or other IRC code in case of error. Needed only on IRC-side
|
||||
"""
|
||||
try:
|
||||
|
@ -27,50 +28,43 @@ def joinChannel(userID = 0, channel = "", token = None, toIRC = True):
|
|||
raise exceptions.userNotFoundException
|
||||
else:
|
||||
token = token
|
||||
userID = token.userID
|
||||
|
||||
# Get usertoken data
|
||||
username = token.username
|
||||
|
||||
# Normal channel, do check stuff
|
||||
# Make sure the channel exists
|
||||
if channel not in glob.channels.channels:
|
||||
raise exceptions.channelUnknownException
|
||||
raise exceptions.channelUnknownException()
|
||||
|
||||
# Check channel permissions
|
||||
# Make sure a game client is not trying to join a #multi_ or #spect_ channel manually
|
||||
channelObject = glob.channels.channels[channel]
|
||||
if channelObject.publicRead == False and token.admin == False:
|
||||
raise exceptions.channelNoPermissionsException
|
||||
|
||||
# Add our userID to users in that channel
|
||||
channelObject.userJoin(userID)
|
||||
if channelObject.isSpecial and not token.irc and not force:
|
||||
raise exceptions.channelUnknownException()
|
||||
|
||||
# Add the channel to our joined channel
|
||||
token.joinChannel(channel)
|
||||
|
||||
# Send channel joined (bancho). We use clientName here because of #multiplayer and #spectator channels
|
||||
token.enqueue(serverPackets.channelJoinSuccess(userID, channelObject.clientName))
|
||||
token.joinChannel(channelObject)
|
||||
|
||||
# Send channel joined (IRC)
|
||||
if glob.irc == True and toIRC == True:
|
||||
glob.ircServer.banchoJoinChannel(username, channel)
|
||||
if glob.irc and not toIRC:
|
||||
glob.ircServer.banchoJoinChannel(token.username, channel)
|
||||
|
||||
# Console output
|
||||
log.info("{} joined channel {}".format(username, channel))
|
||||
log.info("{} joined channel {}".format(token.username, channel))
|
||||
|
||||
# IRC code return
|
||||
return 0
|
||||
except exceptions.channelNoPermissionsException:
|
||||
log.warning("{} attempted to join channel {}, but they have no read permissions".format(username, channel))
|
||||
log.warning("{} attempted to join channel {}, but they have no read permissions".format(token.username, channel))
|
||||
return 403
|
||||
except exceptions.channelUnknownException:
|
||||
log.warning("{} attempted to join an unknown channel ({})".format(username, channel))
|
||||
log.warning("{} attempted to join an unknown channel ({})".format(token.username, channel))
|
||||
return 403
|
||||
except exceptions.userAlreadyInChannelException:
|
||||
log.warning("User {} already in channel {}".format(token.username, channel))
|
||||
return 403
|
||||
except exceptions.userNotFoundException:
|
||||
log.warning("User not connected to IRC/Bancho")
|
||||
return 403 # idk
|
||||
|
||||
def partChannel(userID = 0, channel = "", token = None, toIRC = True, kick = False):
|
||||
def partChannel(userID = 0, channel = "", token = None, toIRC = True, kick = False, force=False):
|
||||
"""
|
||||
Part a channel
|
||||
|
||||
|
@ -79,21 +73,22 @@ def partChannel(userID = 0, channel = "", token = None, toIRC = True, kick = Fal
|
|||
:param channel: channel name
|
||||
:param toIRC: if True, send this channel join event to IRC. Must be true if joining from bancho. Optional. Default: True
|
||||
:param kick: if True, channel tab will be closed on client. Used when leaving lobby. Optional. Default: False
|
||||
:param force: whether to allow game clients to part #spect_ and #multi_ channels
|
||||
:return: 0 if joined or other IRC code in case of error. Needed only on IRC-side
|
||||
"""
|
||||
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
|
||||
if token is None:
|
||||
token = glob.tokens.getTokenFromUserID(userID)
|
||||
# Make sure the token exists
|
||||
if token is None:
|
||||
raise exceptions.userNotFoundException
|
||||
raise exceptions.userNotFoundException()
|
||||
else:
|
||||
token = token
|
||||
userID = token.userID
|
||||
|
||||
# Get usertoken data
|
||||
username = token.username
|
||||
|
||||
# Determine internal/client name if needed
|
||||
# (toclient is used clientwise for #multiplayer and #spectator channels)
|
||||
|
@ -113,12 +108,24 @@ def partChannel(userID = 0, channel = "", token = None, toIRC = True, kick = Fal
|
|||
|
||||
# Make sure the channel exists
|
||||
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)
|
||||
channelObject = glob.channels.channels[channel]
|
||||
token.partChannel(channel)
|
||||
channelObject.userPart(userID)
|
||||
token.partChannel(channelObject)
|
||||
|
||||
# Delete temporary channel if everyone left
|
||||
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
|
||||
# NOTE: Maybe always needed, will check later
|
||||
|
@ -126,17 +133,20 @@ def partChannel(userID = 0, channel = "", token = None, toIRC = True, kick = Fal
|
|||
token.enqueue(serverPackets.channelKicked(channelClient))
|
||||
|
||||
# IRC part
|
||||
if glob.irc == True and toIRC == True:
|
||||
glob.ircServer.banchoPartChannel(username, channel)
|
||||
if glob.irc and toIRC:
|
||||
glob.ircServer.banchoPartChannel(token.username, channel)
|
||||
|
||||
# Console output
|
||||
log.info("{} parted channel {} ({})".format(username, channel, channelClient))
|
||||
log.info("{} parted channel {} ({})".format(token.username, channel, channelClient))
|
||||
|
||||
# Return IRC code
|
||||
return 0
|
||||
except exceptions.channelUnknownException:
|
||||
log.warning("{} attempted to part an unknown channel ({})".format(username, channel))
|
||||
log.warning("{} attempted to part an unknown channel ({})".format(token.username, channel))
|
||||
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:
|
||||
log.warning("User not connected to IRC/Bancho")
|
||||
return 442 # idk
|
||||
|
@ -153,24 +163,20 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
|
|||
:return: 0 if joined or other IRC code in case of error. Needed only on IRC-side
|
||||
"""
|
||||
try:
|
||||
tokenString = ""
|
||||
#tokenString = ""
|
||||
# Get token object if not passed
|
||||
if token is None:
|
||||
token = glob.tokens.getTokenFromUsername(fro)
|
||||
if token is None:
|
||||
raise exceptions.userNotFoundException
|
||||
raise exceptions.userNotFoundException()
|
||||
else:
|
||||
# token object alredy passed, get its string and its username (fro)
|
||||
fro = token.username
|
||||
tokenString = token.token
|
||||
|
||||
# Set some variables
|
||||
userID = token.userID
|
||||
username = token.username
|
||||
#tokenString = token.token
|
||||
|
||||
# Make sure this is not a tournament client
|
||||
if token.tournament:
|
||||
raise exceptions.userTournamentException()
|
||||
# if token.tournament:
|
||||
# raise exceptions.userTournamentException()
|
||||
|
||||
# Make sure the user is not in restricted mode
|
||||
if token.restricted:
|
||||
|
@ -180,12 +186,16 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
|
|||
if token.isSilenced():
|
||||
raise exceptions.userSilencedException()
|
||||
|
||||
# Redirect !report to FokaBot
|
||||
if message.startswith("!report"):
|
||||
to = glob.BOT_NAME
|
||||
|
||||
# Determine internal name if needed
|
||||
# (toclient is used clientwise for #multiplayer and #spectator channels)
|
||||
toClient = to
|
||||
if to == "#spectator":
|
||||
if token.spectating is None:
|
||||
s = userID
|
||||
s = token.userID
|
||||
else:
|
||||
s = token.spectatingUserID
|
||||
to = "#spect_{}".format(s)
|
||||
|
@ -196,6 +206,10 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
|
|||
elif to.startswith("#multi_"):
|
||||
toClient = "#multiplayer"
|
||||
|
||||
# Make sure the message is valid
|
||||
if not message.strip():
|
||||
raise exceptions.invalidArgumentsException()
|
||||
|
||||
# Truncate message if > 2048 characters
|
||||
message = message[:2048]+"..." if len(message) > 2048 else message
|
||||
|
||||
|
@ -203,7 +217,7 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
|
|||
message = glob.chatFilters.filterMessage(message)
|
||||
|
||||
# Build packet bytes
|
||||
packet = serverPackets.sendMessage(username, toClient, message)
|
||||
packet = serverPackets.sendMessage(token.username, toClient, message)
|
||||
|
||||
# Send the message
|
||||
isChannel = to.startswith("#")
|
||||
|
@ -211,94 +225,103 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
|
|||
# CHANNEL
|
||||
# Make sure the channel exists
|
||||
if to not in glob.channels.channels:
|
||||
raise exceptions.channelUnknownException
|
||||
raise exceptions.channelUnknownException()
|
||||
|
||||
# Make sure the channel is not in moderated mode
|
||||
if glob.channels.channels[to].moderated == True and token.admin == False:
|
||||
raise exceptions.channelModeratedException
|
||||
if glob.channels.channels[to].moderated and not token.admin:
|
||||
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
|
||||
if glob.channels.channels[to].publicWrite == False and token.admin == False:
|
||||
raise exceptions.channelNoPermissionsException
|
||||
if not glob.channels.channels[to].publicWrite and not token.admin:
|
||||
raise exceptions.channelNoPermissionsException()
|
||||
|
||||
# Add message in buffer
|
||||
token.addMessageInBuffer(to, message)
|
||||
|
||||
# Everything seems fine, build recipients list and send packet
|
||||
recipients = glob.channels.channels[to].connectedUsers[:]
|
||||
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)
|
||||
glob.streams.broadcast("chat/{}".format(to), packet, but=[token.token])
|
||||
else:
|
||||
# USER
|
||||
# Make sure recipient user is connected
|
||||
recipientToken = glob.tokens.getTokenFromUsername(to)
|
||||
if recipientToken is None:
|
||||
raise exceptions.userNotFoundException
|
||||
raise exceptions.userNotFoundException()
|
||||
|
||||
# Make sure the recipient is not a tournament client
|
||||
if recipientToken.tournament:
|
||||
raise exceptions.userTournamentException()
|
||||
#if recipientToken.tournament:
|
||||
# raise exceptions.userTournamentException()
|
||||
|
||||
# Make sure the recipient is not restricted or we are FokaBot
|
||||
if recipientToken.restricted == True and fro.lower() != "fokabot":
|
||||
if recipientToken.restricted and fro.lower() != glob.BOT_NAME.lower():
|
||||
raise exceptions.userRestrictedException()
|
||||
|
||||
# TODO: Make sure the recipient has not disabled PMs for non-friends or he's our friend
|
||||
|
||||
# Away check
|
||||
if recipientToken.awayCheck(userID):
|
||||
sendMessage(to, fro, "\x01ACTION is away: {message}\x01".format(code=chr(int(1)), message=recipientToken.awayMessage))
|
||||
if recipientToken.awayCheck(token.userID):
|
||||
sendMessage(to, fro, "\x01ACTION is away: {}\x01".format(recipientToken.awayMessage))
|
||||
|
||||
# Check message templates (mods/admins only)
|
||||
if message in messageTemplates.templates and token.admin == True:
|
||||
if message in messageTemplates.templates and token.admin:
|
||||
sendMessage(fro, to, messageTemplates.templates[message])
|
||||
|
||||
# Everything seems fine, send packet
|
||||
recipientToken.enqueue(packet)
|
||||
|
||||
# Send the message to IRC
|
||||
if glob.irc == True and toIRC == True:
|
||||
glob.ircServer.banchoMessage(fro, to, message)
|
||||
if glob.irc and toIRC:
|
||||
messageSplitInLines = message.encode("latin-1").decode("utf-8").split("\n")
|
||||
for line in messageSplitInLines:
|
||||
if line == messageSplitInLines[:1] and line == "":
|
||||
continue
|
||||
glob.ircServer.banchoMessage(fro, to, line)
|
||||
|
||||
# Spam protection (ignore FokaBot)
|
||||
if userID > 999:
|
||||
if token.userID > 999:
|
||||
token.spamProtection()
|
||||
|
||||
# Fokabot message
|
||||
if isChannel == True or to.lower() == "fokabot":
|
||||
fokaMessage = fokabot.fokabotResponse(username, to, message)
|
||||
if isChannel or to.lower() == glob.BOT_NAME.lower():
|
||||
fokaMessage = fokabot.fokabotResponse(token.username, to, message)
|
||||
if fokaMessage:
|
||||
sendMessage("FokaBot", to if isChannel else fro, fokaMessage)
|
||||
sendMessage(glob.BOT_NAME, to if isChannel else fro, fokaMessage)
|
||||
|
||||
# File and discord logs (public chat only)
|
||||
if to.startswith("#") and not (message.startswith("\x01ACTION is playing") and to.startswith("#spect_")):
|
||||
log.chat("{fro} @ {to}: {message}".format(fro=username, to=to, message=str(message.encode("utf-8"))))
|
||||
glob.schiavo.sendChatlog("**{fro} @ {to}:** {message}".format(fro=username, to=to, message=str(message.encode("utf-8"))[2:-1]))
|
||||
log.chat("{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=token.username, to=to, message=message.encode("latin-1").decode("utf-8")))
|
||||
return 0
|
||||
except exceptions.userSilencedException:
|
||||
token.enqueue(serverPackets.silenceEndTime(token.getSilenceSecondsLeft()))
|
||||
log.warning("{} tried to send a message during silence".format(username))
|
||||
log.warning("{} tried to send a message during silence".format(token.username))
|
||||
return 404
|
||||
except exceptions.channelModeratedException:
|
||||
log.warning("{} tried to send a message to a channel that is in moderated mode ({})".format(username, to))
|
||||
log.warning("{} tried to send a message to a channel that is in moderated mode ({})".format(token.username, to))
|
||||
return 404
|
||||
except exceptions.channelUnknownException:
|
||||
log.warning("{} tried to send a message to an unknown channel ({})".format(username, to))
|
||||
log.warning("{} tried to send a message to an unknown channel ({})".format(token.username, to))
|
||||
return 403
|
||||
except exceptions.channelNoPermissionsException:
|
||||
log.warning("{} tried to send a message to channel {}, but they have no write permissions".format(username, to))
|
||||
log.warning("{} tried to send a message to channel {}, but they have no write permissions".format(token.username, to))
|
||||
return 404
|
||||
except exceptions.userRestrictedException:
|
||||
log.warning("{} tried to send a message {}, but the recipient is in restricted mode".format(username, to))
|
||||
log.warning("{} tried to send a message {}, but the recipient is in restricted mode".format(token.username, to))
|
||||
return 404
|
||||
except exceptions.userTournamentException:
|
||||
log.warning("{} tried to send a message {}, but the recipient is a tournament client".format(username, to))
|
||||
log.warning("{} tried to send a message {}, but the recipient is a tournament client".format(token.username, to))
|
||||
return 404
|
||||
except exceptions.userNotFoundException:
|
||||
log.warning("User not connected to IRC/Bancho")
|
||||
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"""
|
||||
|
|
|
@ -48,16 +48,16 @@ class config:
|
|||
self.config.get("server","gziplevel")
|
||||
self.config.get("server","cikey")
|
||||
|
||||
self.config.get("mirror","url")
|
||||
self.config.get("mirror","apikey")
|
||||
self.config.get("cheesegull", "apiurl")
|
||||
self.config.get("cheesegull", "apikey")
|
||||
|
||||
self.config.get("debug","enable")
|
||||
self.config.get("debug","packets")
|
||||
self.config.get("debug","time")
|
||||
|
||||
self.config.get("sentry","enable")
|
||||
self.config.get("sentry","banchodns")
|
||||
self.config.get("sentry","ircdns")
|
||||
self.config.get("sentry","banchodsn")
|
||||
self.config.get("sentry","ircdsn")
|
||||
|
||||
self.config.get("discord","enable")
|
||||
self.config.get("discord","boturl")
|
||||
|
@ -73,8 +73,10 @@ class config:
|
|||
|
||||
self.config.get("localize","enable")
|
||||
self.config.get("localize","ipapiurl")
|
||||
|
||||
self.config.get("custom", "config")
|
||||
return True
|
||||
except:
|
||||
except configparser.Error:
|
||||
return False
|
||||
|
||||
def generateDefaultConfig(self):
|
||||
|
@ -107,9 +109,9 @@ class config:
|
|||
self.config.set("server", "gziplevel", "6")
|
||||
self.config.set("server", "cikey", "changeme")
|
||||
|
||||
self.config.add_section("mirror")
|
||||
self.config.set("mirror", "url", "http://storage.ripple.moe")
|
||||
self.config.set("mirror", "apikey", "anotherkey")
|
||||
self.config.add_section("cheesegull")
|
||||
self.config.set("cheesegull", "apiurl", "http://cheesegu.ll/api")
|
||||
self.config.set("cheesegull", "apikey", "")
|
||||
|
||||
self.config.add_section("debug")
|
||||
self.config.set("debug", "enable", "0")
|
||||
|
@ -118,8 +120,8 @@ class config:
|
|||
|
||||
self.config.add_section("sentry")
|
||||
self.config.set("sentry", "enable", "0")
|
||||
self.config.set("sentry", "banchodns", "")
|
||||
self.config.set("sentry", "ircdns", "")
|
||||
self.config.set("sentry", "banchodsn", "")
|
||||
self.config.set("sentry", "ircdsn", "")
|
||||
|
||||
self.config.add_section("discord")
|
||||
self.config.set("discord", "enable", "0")
|
||||
|
@ -140,6 +142,9 @@ class config:
|
|||
self.config.set("localize", "enable", "1")
|
||||
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
|
||||
self.config.write(f)
|
||||
f.close()
|
||||
|
|
|
@ -27,8 +27,11 @@ def printServerStartHeader(asciiArt=True):
|
|||
print("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^{}".format(bcolors.ENDC))
|
||||
|
||||
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("> {}https://git.zxq.co/ripple/pep.py".format(bcolors.UNDERLINE), bcolors.GREEN)
|
||||
printColored("> {}https://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)
|
||||
|
||||
def printNoNl(string):
|
||||
|
|
|
@ -30,7 +30,7 @@ def getLocation(ip):
|
|||
try:
|
||||
# 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(",")
|
||||
return (float(result[0]), float(result[1]))
|
||||
return float(result[0]), float(result[1])
|
||||
except:
|
||||
log.error("Error in get position")
|
||||
return (0, 0)
|
||||
return 0, 0
|
||||
|
|
|
@ -136,7 +136,7 @@ cpdef bytes packData(__data, int dataType):
|
|||
|
||||
return data
|
||||
|
||||
cpdef bytes buildPacket(int __packet, list __packetData = []):
|
||||
cpdef bytes buildPacket(int __packet, list __packetData = None):
|
||||
"""
|
||||
Builds a packet
|
||||
|
||||
|
@ -144,6 +144,9 @@ cpdef bytes buildPacket(int __packet, list __packetData = []):
|
|||
:param __packetData: packet structure [[data, dataType], [data, dataType], ...]
|
||||
:return: packet bytes
|
||||
"""
|
||||
# Default argument
|
||||
if __packetData is None:
|
||||
__packetData = []
|
||||
# Set some variables
|
||||
cdef bytes packetData = bytes()
|
||||
cdef int packetLength = 0
|
||||
|
@ -183,7 +186,7 @@ cpdef int readPacketLength(bytes stream):
|
|||
return unpackData(stream[3:7], dataTypes.UINT32)
|
||||
|
||||
|
||||
cpdef readPacketData(bytes stream, list structure=[], bint hasFirstBytes = True):
|
||||
cpdef readPacketData(bytes stream, list structure=None, bint hasFirstBytes = True):
|
||||
"""
|
||||
Read packet data from `stream` according to `structure`
|
||||
:param stream: packet bytes
|
||||
|
@ -192,6 +195,10 @@ cpdef readPacketData(bytes stream, list structure=[], bint hasFirstBytes = True)
|
|||
if False, `stream` has only packet data. Default: True
|
||||
:return: {name: unpackedValue, ...}
|
||||
"""
|
||||
# Default list argument
|
||||
if structure is None:
|
||||
structure = []
|
||||
|
||||
# Read packet ID (first 2 bytes)
|
||||
cdef dict data = {}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ def scheduleShutdown(sendRestartTime, restart, message = "", delay=20):
|
|||
:return:
|
||||
"""
|
||||
# Console output
|
||||
log.info("Pep.py will {} in {} seconds!".format("restart" if restart else "shutdown", sendRestartTime+delay))
|
||||
log.info("Pep.py will {} in {} seconds!".format("restart" if restart else "shutdown", sendRestartTime+delay), "bunker")
|
||||
log.info("Sending server restart packets in {} seconds...".format(sendRestartTime))
|
||||
|
||||
# Send notification if set
|
||||
|
|
|
@ -17,6 +17,7 @@ import traceback
|
|||
import raven
|
||||
|
||||
from common.log import logUtils as log
|
||||
from common.ripple import userUtils
|
||||
from helpers import chatHelper as chat
|
||||
from objects import glob
|
||||
|
||||
|
@ -44,6 +45,7 @@ class Client:
|
|||
self.IRCUsername = ""
|
||||
self.banchoUsername = ""
|
||||
self.supposedUsername = ""
|
||||
self.supposedUserID = 0
|
||||
self.joinedChannels = []
|
||||
|
||||
def messageChannel(self, channel, command, message, includeSelf=False):
|
||||
|
@ -136,7 +138,7 @@ class Client:
|
|||
self.server.removeClient(self, quitmsg)
|
||||
|
||||
# Bancho logout
|
||||
if callLogout:
|
||||
if callLogout and self.banchoUsername != "":
|
||||
chat.IRCDisconnect(self.IRCUsername)
|
||||
|
||||
|
||||
|
@ -280,9 +282,10 @@ class Client:
|
|||
m = hashlib.md5()
|
||||
m.update(arguments[0].encode("utf-8"))
|
||||
tokenHash = m.hexdigest()
|
||||
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 supposedUsername:
|
||||
self.supposedUsername = chat.fixUsernameForIRC(supposedUsername["username"])
|
||||
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])
|
||||
if supposedUser:
|
||||
self.supposedUsername = chat.fixUsernameForIRC(supposedUser["username"])
|
||||
self.supposedUserID = supposedUser["id"]
|
||||
self.__handleCommand = self.registerHandler
|
||||
else:
|
||||
# Wrong IRC Token
|
||||
|
@ -310,6 +313,11 @@ class Client:
|
|||
self.reply("464 :Password incorrect")
|
||||
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
|
||||
token = glob.tokens.getTokenFromUsername(chat.fixUsernameForBancho(nick), True)
|
||||
if token is not None:
|
||||
|
@ -350,11 +358,11 @@ class Client:
|
|||
self.sendMotd()
|
||||
self.__handleCommand = self.mainHandler
|
||||
|
||||
def quitHandler(self, command, arguments):
|
||||
def quitHandler(self, _, arguments):
|
||||
"""QUIT command handler"""
|
||||
self.disconnect(self.IRCUsername if len(arguments) < 1 else arguments[0])
|
||||
|
||||
def joinHandler(self, command, arguments):
|
||||
def joinHandler(self, _, arguments):
|
||||
"""JOIN command handler"""
|
||||
if len(arguments) < 1:
|
||||
self.reply461("JOIN")
|
||||
|
@ -402,13 +410,15 @@ class Client:
|
|||
self.replyCode(332, description, channel=channel)
|
||||
|
||||
# Build connected users list
|
||||
users = glob.channels.channels[channel].connectedUsers[:]
|
||||
if "chat/{}".format(channel) not in glob.streams.streams:
|
||||
self.reply403(channel)
|
||||
continue
|
||||
users = glob.streams.streams["chat/{}".format(channel)].clients
|
||||
usernames = []
|
||||
for user in users:
|
||||
token = glob.tokens.getTokenFromUserID(user)
|
||||
if token is None:
|
||||
if user not in glob.tokens.tokens:
|
||||
continue
|
||||
usernames.append(chat.fixUsernameForIRC(token.username))
|
||||
usernames.append(chat.fixUsernameForIRC(glob.tokens.tokens[user].username))
|
||||
usernames = " ".join(usernames)
|
||||
|
||||
# Send IRC users list
|
||||
|
@ -419,7 +429,7 @@ class Client:
|
|||
self.reply403(channel)
|
||||
continue
|
||||
|
||||
def partHandler(self, command, arguments):
|
||||
def partHandler(self, _, arguments):
|
||||
"""PART command handler"""
|
||||
if len(arguments) < 1:
|
||||
self.reply461("PART")
|
||||
|
@ -503,7 +513,7 @@ class Client:
|
|||
"""LUSERS command handler"""
|
||||
self.sendLusers()
|
||||
|
||||
def pingHandler(self, command, arguments):
|
||||
def pingHandler(self, _, arguments):
|
||||
"""PING command handler"""
|
||||
if len(arguments) < 1:
|
||||
self.replyCode(409, "No origin specified")
|
||||
|
@ -514,7 +524,7 @@ class Client:
|
|||
"""(fake) PONG command handler"""
|
||||
pass
|
||||
|
||||
def awayHandler(self, command, arguments):
|
||||
def awayHandler(self, _, arguments):
|
||||
"""AWAY command handler"""
|
||||
response = chat.IRCAway(self.banchoUsername, " ".join(arguments))
|
||||
self.replyCode(response, "You are no longer marked as being away" if response == 305 else "You have been marked as being away")
|
||||
|
@ -619,12 +629,11 @@ class Server:
|
|||
value.message(":{} PRIVMSG {} :{}".format(fro, to, message))
|
||||
|
||||
|
||||
def removeClient(self, client, quitmsg):
|
||||
def removeClient(self, client, _):
|
||||
"""
|
||||
Remove a client from connected clients
|
||||
|
||||
:param client: client object
|
||||
:param quitmsg: QUIT argument, useless atm
|
||||
:return:
|
||||
"""
|
||||
if client.socket in self.clients:
|
||||
|
@ -637,8 +646,9 @@ class Server:
|
|||
:return:
|
||||
"""
|
||||
# Sentry
|
||||
sentryClient = None
|
||||
if glob.sentry:
|
||||
sentryClient = raven.Client(glob.conf.config["sentry"]["ircdns"])
|
||||
sentryClient = raven.Client(glob.conf.config["sentry"]["ircdsn"])
|
||||
|
||||
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
|
@ -669,7 +679,7 @@ class Server:
|
|||
try:
|
||||
self.clients[conn] = Client(self, conn)
|
||||
log.info("[IRC] Accepted connection from {}:{}".format(addr[0], addr[1]))
|
||||
except socket.error as e:
|
||||
except socket.error:
|
||||
try:
|
||||
conn.close()
|
||||
except:
|
||||
|
@ -688,7 +698,7 @@ class Server:
|
|||
lastAliveCheck = now
|
||||
except:
|
||||
log.error("[IRC] Unknown error!\n```\n{}\n{}```".format(sys.exc_info(), traceback.format_exc()))
|
||||
if glob.sentry:
|
||||
if glob.sentry and sentryClient is not None:
|
||||
sentryClient.captureException()
|
||||
|
||||
def main(port=6667):
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
from common import generalUtils
|
||||
from constants import serverPackets
|
||||
from objects import glob
|
||||
from common.log import logUtils as log
|
||||
|
||||
|
||||
class banchoConfig:
|
||||
|
@ -30,7 +31,12 @@ class banchoConfig:
|
|||
"""
|
||||
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["menuIcon"] = glob.db.fetch("SELECT value_string FROM bancho_settings WHERE name = 'menu_icon'")["value_string"]
|
||||
mainMenuIcon = glob.db.fetch("SELECT file_id, url FROM main_menu_icons WHERE is_current = 1 LIMIT 1")
|
||||
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"]
|
||||
|
||||
|
||||
|
@ -57,5 +63,5 @@ class banchoConfig:
|
|||
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 == True and value.hidden == False:
|
||||
if value.publicRead and not value.hidden:
|
||||
glob.streams.broadcast("main", serverPackets.channelInfo(key))
|
|
@ -1,3 +1,6 @@
|
|||
import logging
|
||||
|
||||
from constants import exceptions
|
||||
from objects import glob
|
||||
|
||||
class channel:
|
||||
|
@ -18,37 +21,24 @@ class channel:
|
|||
self.publicWrite = publicWrite
|
||||
self.moderated = False
|
||||
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
|
||||
|
||||
# Client name (#spectator/#multiplayer)
|
||||
self.clientName = self.name
|
||||
# Make Foka join the channel
|
||||
fokaToken = glob.tokens.getTokenFromUserID(999)
|
||||
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_"):
|
||||
self.clientName = "#spectator"
|
||||
return "#spectator"
|
||||
elif self.name.startswith("#multi_"):
|
||||
self.clientName = "#multiplayer"
|
||||
|
||||
def userJoin(self, userID):
|
||||
"""
|
||||
Add a user to connected users
|
||||
|
||||
:param userID:
|
||||
:return:
|
||||
"""
|
||||
if userID not in self.connectedUsers:
|
||||
self.connectedUsers.append(userID)
|
||||
|
||||
def userPart(self, userID):
|
||||
"""
|
||||
Remove a user from connected users
|
||||
|
||||
:param userID:
|
||||
:return:
|
||||
"""
|
||||
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)
|
||||
return "#multiplayer"
|
||||
return self.name
|
|
@ -1,6 +1,7 @@
|
|||
from common.log import logUtils as log
|
||||
from objects import channel
|
||||
from objects import glob
|
||||
from helpers import chatHelper as chat
|
||||
|
||||
|
||||
class channelList:
|
||||
|
@ -34,6 +35,7 @@ class channelList:
|
|||
:param hidden: if True, thic channel won't be shown in channels list
|
||||
:return:
|
||||
"""
|
||||
glob.streams.add("chat/{}".format(name))
|
||||
self.channels[name] = channel.channel(name, description, publicRead, publicWrite, temp, hidden)
|
||||
log.info("Created channel {}".format(name))
|
||||
|
||||
|
@ -47,9 +49,24 @@ class channelList:
|
|||
"""
|
||||
if name in self.channels:
|
||||
return False
|
||||
glob.streams.add("chat/{}".format(name))
|
||||
self.channels[name] = channel.channel(name, "Chat", True, True, True, True)
|
||||
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):
|
||||
"""
|
||||
Removes a channel from channels list
|
||||
|
@ -60,5 +77,13 @@ class channelList:
|
|||
if name not in self.channels:
|
||||
log.debug("{} is not in channels list".format(name))
|
||||
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)
|
||||
log.info("Removed channel {}".format(name))
|
||||
|
|
|
@ -36,6 +36,8 @@ class chatFilters:
|
|||
:param message: normal message
|
||||
:return: filtered message
|
||||
"""
|
||||
return message
|
||||
"""
|
||||
# Split words by spaces
|
||||
messageTemp = message.split(" ")
|
||||
|
||||
|
@ -49,3 +51,4 @@ class chatFilters:
|
|||
|
||||
# Return filtered message
|
||||
return message
|
||||
"""
|
||||
|
|
|
@ -17,6 +17,7 @@ def connect():
|
|||
|
||||
:return:
|
||||
"""
|
||||
glob.BOT_NAME = userUtils.getUsername(999)
|
||||
token = glob.tokens.addToken(999)
|
||||
token.actionID = actions.IDLE
|
||||
glob.streams.broadcast("main", serverPackets.userPanel(999))
|
||||
|
@ -41,7 +42,7 @@ def fokabotResponse(fro, chan, message):
|
|||
"""
|
||||
for i in fokabotCommands.commands:
|
||||
# Loop though all commands
|
||||
if generalUtils.strContains(message, i["trigger"]):
|
||||
if re.compile("^{}( (.+)?)?$".format(i["trigger"])).match(message.strip()):
|
||||
# message has triggered a command
|
||||
|
||||
# Make sure the user has right permissions
|
||||
|
|
|
@ -11,13 +11,14 @@ from common.web import schiavo
|
|||
|
||||
try:
|
||||
with open("version") as f:
|
||||
VERSION = f.read()
|
||||
VERSION = f.read().strip()
|
||||
if VERSION == "":
|
||||
raise Exception
|
||||
except:
|
||||
VERSION = "Unknown"
|
||||
|
||||
DATADOG_PREFIX = "peppy"
|
||||
BOT_NAME = "FokaBot"
|
||||
application = None
|
||||
db = None
|
||||
redis = None
|
||||
|
@ -48,3 +49,11 @@ restarting = False
|
|||
startTime = int(time.time())
|
||||
|
||||
streams = streamList.streamList()
|
||||
|
||||
# Additional modifications
|
||||
COMMON_VERSION_REQ = "1.2.1"
|
||||
try:
|
||||
with open("common/version") as f:
|
||||
COMMON_VERSION = f.read().strip()
|
||||
except:
|
||||
COMMON_VERSION = "Unknown"
|
322
objects/match.py
322
objects/match.py
|
@ -1,4 +1,9 @@
|
|||
import copy
|
||||
import json
|
||||
import threading
|
||||
|
||||
import time
|
||||
|
||||
from common.log import logUtils as log
|
||||
from constants import dataTypes
|
||||
from constants import matchModModes
|
||||
|
@ -14,16 +19,19 @@ from objects import glob
|
|||
class slot:
|
||||
def __init__(self):
|
||||
self.status = slotStatuses.FREE
|
||||
self.team = 0
|
||||
self.team = matchTeams.NO_TEAM
|
||||
self.userID = -1
|
||||
self.user = None
|
||||
self.mods = 0
|
||||
self.loaded = False
|
||||
self.skip = False
|
||||
self.complete = False
|
||||
self.score = 0
|
||||
self.failed = False
|
||||
self.passed = True
|
||||
|
||||
class match:
|
||||
def __init__(self, matchID, matchName, matchPassword, beatmapID, beatmapName, beatmapMD5, gameMode, hostUserID):
|
||||
def __init__(self, matchID, matchName, matchPassword, beatmapID, beatmapName, beatmapMD5, gameMode, hostUserID, isTourney=False):
|
||||
"""
|
||||
Create a new match object
|
||||
|
||||
|
@ -53,6 +61,13 @@ class match:
|
|||
self.matchModMode = matchModModes.NORMAL # default value
|
||||
self.seed = 0
|
||||
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
|
||||
self.slots = []
|
||||
|
@ -64,59 +79,67 @@ class match:
|
|||
glob.streams.add(self.playingStreamName)
|
||||
|
||||
# Create #multiplayer channel
|
||||
glob.channels.addTempChannel("#multi_{}".format(self.matchID))
|
||||
glob.channels.addHiddenChannel("#multi_{}".format(self.matchID))
|
||||
log.info("MPROOM{}: {} match created!".format(self.matchID, "Tourney" if self.isTourney else "Normal"))
|
||||
|
||||
def getMatchData(self):
|
||||
def getMatchData(self, censored = False):
|
||||
"""
|
||||
Return binary match data structure for packetHelper
|
||||
Return binary match data structure for packetHelper
|
||||
|
||||
:return:
|
||||
"""
|
||||
# General match info
|
||||
# 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 = [
|
||||
[safeMatch.matchID, dataTypes.UINT16],
|
||||
[int(safeMatch.inProgress), dataTypes.BYTE],
|
||||
[self.matchID, dataTypes.UINT16],
|
||||
[int(self.inProgress), dataTypes.BYTE],
|
||||
[0, dataTypes.BYTE],
|
||||
[safeMatch.mods, dataTypes.UINT32],
|
||||
[safeMatch.matchName, dataTypes.STRING],
|
||||
[safeMatch.matchPassword, dataTypes.STRING],
|
||||
[safeMatch.beatmapName, dataTypes.STRING],
|
||||
[safeMatch.beatmapID, dataTypes.UINT32],
|
||||
[safeMatch.beatmapMD5, dataTypes.STRING],
|
||||
[self.mods, dataTypes.UINT32],
|
||||
[self.matchName, 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
|
||||
for i in range(0,16):
|
||||
struct.append([safeMatch.slots[i].status, dataTypes.BYTE])
|
||||
struct.append([self.slots[i].status, dataTypes.BYTE])
|
||||
|
||||
# Slot teams, always 16 elements
|
||||
for i in range(0,16):
|
||||
struct.append([safeMatch.slots[i].team, dataTypes.BYTE])
|
||||
struct.append([self.slots[i].team, dataTypes.BYTE])
|
||||
|
||||
# Slot user ID. Write only if slot is occupied
|
||||
for i in range(0,16):
|
||||
if safeMatch.slots[i].user is not None and safeMatch.slots[i].user in glob.tokens.tokens:
|
||||
struct.append([glob.tokens.tokens[safeMatch.slots[i].user].userID, dataTypes.UINT32])
|
||||
if self.slots[i].user is not None and self.slots[i].user in glob.tokens.tokens:
|
||||
struct.append([glob.tokens.tokens[self.slots[i].user].userID, dataTypes.UINT32])
|
||||
|
||||
# Other match data
|
||||
struct.extend([
|
||||
[safeMatch.hostUserID, dataTypes.SINT32],
|
||||
[safeMatch.gameMode, dataTypes.BYTE],
|
||||
[safeMatch.matchScoringType, dataTypes.BYTE],
|
||||
[safeMatch.matchTeamType, dataTypes.BYTE],
|
||||
[safeMatch.matchModMode, dataTypes.BYTE],
|
||||
[self.hostUserID, dataTypes.SINT32],
|
||||
[self.gameMode, dataTypes.BYTE],
|
||||
[self.matchScoringType, dataTypes.BYTE],
|
||||
[self.matchTeamType, dataTypes.BYTE],
|
||||
[self.matchModMode, dataTypes.BYTE],
|
||||
])
|
||||
|
||||
# Slot mods if free mod is enabled
|
||||
if safeMatch.matchModMode == matchModModes.FREE_MOD:
|
||||
if self.matchModMode == matchModModes.FREE_MOD:
|
||||
for i in range(0,16):
|
||||
struct.append([safeMatch.slots[i].mods, dataTypes.UINT32])
|
||||
struct.append([self.slots[i].mods, dataTypes.UINT32])
|
||||
|
||||
# Seed idk
|
||||
# TODO: Implement this, it should be used for mania "random" mod
|
||||
struct.append([safeMatch.seed, dataTypes.UINT32])
|
||||
struct.append([self.seed, dataTypes.UINT32])
|
||||
|
||||
return struct
|
||||
|
||||
|
@ -129,12 +152,22 @@ class match:
|
|||
"""
|
||||
slotID = self.getUserSlotID(newHost)
|
||||
if slotID is None or self.slots[slotID].user not in glob.tokens.tokens:
|
||||
return
|
||||
return False
|
||||
token = glob.tokens.tokens[self.slots[slotID].user]
|
||||
self.hostUserID = newHost
|
||||
token.enqueue(serverPackets.matchTransferHost())
|
||||
self.sendUpdates()
|
||||
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):
|
||||
"""
|
||||
|
@ -195,6 +228,8 @@ class match:
|
|||
:return:
|
||||
"""
|
||||
# Update ready status and setnd update
|
||||
if self.slots[slotID].user is None or self.isStarting:
|
||||
return
|
||||
oldStatus = self.slots[slotID].status
|
||||
if oldStatus == slotStatuses.READY:
|
||||
newStatus = slotStatuses.NOT_READY
|
||||
|
@ -204,7 +239,7 @@ class match:
|
|||
self.sendUpdates()
|
||||
log.info("MPROOM{}: Slot{} changed ready status to {}".format(self.matchID, slotID, self.slots[slotID].status))
|
||||
|
||||
def toggleSlotLock(self, slotID):
|
||||
def toggleSlotLocked(self, slotID):
|
||||
"""
|
||||
Lock a slot
|
||||
Same as calling setSlot and then sendUpdate
|
||||
|
@ -218,7 +253,7 @@ class match:
|
|||
else:
|
||||
newStatus = slotStatuses.LOCKED
|
||||
|
||||
# Send updated settings to kicked user, so he returns to lobby
|
||||
# Send updated settings to kicked user, so they will return to the lobby.
|
||||
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))
|
||||
|
||||
|
@ -305,6 +340,26 @@ class match:
|
|||
glob.streams.broadcast(self.playingStreamName, serverPackets.allPlayersSkipped())
|
||||
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):
|
||||
"""
|
||||
Set userID's slot completed to True
|
||||
|
@ -337,16 +392,35 @@ class match:
|
|||
|
||||
: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
|
||||
self.inProgress = False
|
||||
|
||||
# Reset slots
|
||||
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.resetSlots()
|
||||
|
||||
# Send match update
|
||||
self.sendUpdates()
|
||||
|
@ -355,11 +429,46 @@ class match:
|
|||
glob.streams.broadcast(self.streamName, serverPackets.matchComplete())
|
||||
|
||||
# Destroy playing stream
|
||||
glob.streams.dispose(self.playingStreamName)
|
||||
glob.streams.remove(self.playingStreamName)
|
||||
|
||||
# Console output
|
||||
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):
|
||||
"""
|
||||
Get slot ID occupied by userID
|
||||
|
@ -375,7 +484,7 @@ class match:
|
|||
"""
|
||||
Add someone to users in match
|
||||
|
||||
:param userID: user id of the user
|
||||
:param user: user object of the user
|
||||
:return: True if join success, False if fail (room is full)
|
||||
"""
|
||||
# Make sure we're not in this match
|
||||
|
@ -388,7 +497,10 @@ class match:
|
|||
for i in range(0,16):
|
||||
if self.slots[i].status == slotStatuses.FREE:
|
||||
# Occupy slot
|
||||
self.setSlot(i, slotStatuses.NOT_READY, 0, user.token, 0)
|
||||
team = matchTeams.NO_TEAM
|
||||
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
|
||||
self.sendUpdates()
|
||||
|
@ -399,11 +511,12 @@ class match:
|
|||
|
||||
return False
|
||||
|
||||
def userLeft(self, user):
|
||||
def userLeft(self, user, disposeMatch=True):
|
||||
"""
|
||||
Remove someone from users in match
|
||||
|
||||
:param userID: user if of the user
|
||||
:param user: user object 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
|
||||
|
@ -415,10 +528,10 @@ class match:
|
|||
self.setSlot(slotID, slotStatuses.FREE, 0, None, 0)
|
||||
|
||||
# Check if everyone left
|
||||
if self.countUsers() == 0:
|
||||
if self.countUsers() == 0 and disposeMatch and not self.isTourney:
|
||||
# Dispose match
|
||||
glob.matches.disposeMatch(self.matchID)
|
||||
log.info("MPROOM{}: Room disposed".format(self.matchID))
|
||||
log.info("MPROOM{}: Room disposed because all users left".format(self.matchID))
|
||||
return
|
||||
|
||||
# Check if host left
|
||||
|
@ -443,14 +556,18 @@ class match:
|
|||
:param 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
|
||||
oldSlotID = self.getUserSlotID(userID)
|
||||
if oldSlotID is None:
|
||||
return
|
||||
return False
|
||||
|
||||
# Make sure there is no one inside new slot
|
||||
if self.slots[newSlotID].user is not None and self.slots[newSlotID].status != slotStatuses.FREE:
|
||||
return
|
||||
if self.slots[newSlotID].user is not None or self.slots[newSlotID].status != slotStatuses.FREE:
|
||||
return False
|
||||
|
||||
# Get old slot data
|
||||
#oldData = dill.copy(self.slots[oldSlotID])
|
||||
|
@ -467,6 +584,7 @@ class match:
|
|||
|
||||
# Console output
|
||||
log.info("MPROOM{}: {} moved to slot {}".format(self.matchID, userID, newSlotID))
|
||||
return True
|
||||
|
||||
def changePassword(self, newPassword):
|
||||
"""
|
||||
|
@ -532,7 +650,7 @@ class match:
|
|||
self.setHost(glob.tokens.tokens[self.slots[slotID].user].userID)
|
||||
|
||||
# Send updates
|
||||
self.sendUpdates()
|
||||
# self.sendUpdates()
|
||||
|
||||
def playerFailed(self, userID):
|
||||
"""
|
||||
|
@ -546,6 +664,8 @@ class match:
|
|||
if slotID is None:
|
||||
return
|
||||
|
||||
self.slots[slotID].passed = False
|
||||
|
||||
# Send packet to everyone
|
||||
glob.streams.broadcast(self.playingStreamName, serverPackets.playerFailed(slotID))
|
||||
|
||||
|
@ -568,7 +688,7 @@ class match:
|
|||
|
||||
# FokaBot is too busy
|
||||
if to == 999:
|
||||
chat.sendMessage("FokaBot", froToken.username, "I would love to join your match, but I'm busy keeping ripple up and running. Sorry. Beep Boop.")
|
||||
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.")
|
||||
|
||||
# Send message
|
||||
message = "Come join my multiplayer match: \"[osump://{}/{} {}]\"".format(self.matchID, self.matchPassword.replace(" ", "_"), self.matchName)
|
||||
|
@ -586,20 +706,29 @@ class match:
|
|||
c+=1
|
||||
return c
|
||||
|
||||
def changeTeam(self, userID):
|
||||
def changeTeam(self, userID, newTeam=None):
|
||||
"""
|
||||
Change userID's team
|
||||
|
||||
:param 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
|
||||
slotID = self.getUserSlotID(userID)
|
||||
if slotID is None:
|
||||
return
|
||||
|
||||
# Update slot and send update
|
||||
newTeam = matchTeams.BLUE if self.slots[slotID].team == matchTeams.RED else matchTeams.RED
|
||||
if newTeam is None:
|
||||
newTeam = matchTeams.BLUE if self.slots[slotID].team == matchTeams.RED else matchTeams.RED
|
||||
self.setSlot(slotID, None, newTeam)
|
||||
self.sendUpdates()
|
||||
|
||||
|
@ -610,9 +739,11 @@ class match:
|
|||
:return:
|
||||
"""
|
||||
self.matchDataCache = serverPackets.updateMatch(self.matchID)
|
||||
censoredDataCache = serverPackets.updateMatch(self.matchID, censored=True)
|
||||
if self.matchDataCache is not None:
|
||||
glob.streams.broadcast(self.streamName, self.matchDataCache)
|
||||
glob.streams.broadcast("lobby", self.matchDataCache)
|
||||
if censoredDataCache is not None:
|
||||
glob.streams.broadcast("lobby", censoredDataCache)
|
||||
else:
|
||||
log.error("MPROOM{}: Can't send match update packet, match data is None!!!".format(self.matchID))
|
||||
|
||||
|
@ -623,7 +754,7 @@ class match:
|
|||
:return: True if valid, False if invalid
|
||||
:return:
|
||||
"""
|
||||
if self.matchTeamType != matchTeamTypes.TEAM_VS or self.matchTeamType != matchTeamTypes.TAG_TEAM_VS:
|
||||
if self.matchTeamType != matchTeamTypes.TEAM_VS and self.matchTeamType != matchTeamTypes.TAG_TEAM_VS:
|
||||
# Teams are always valid if we have no teams
|
||||
return True
|
||||
|
||||
|
@ -646,20 +777,23 @@ class match:
|
|||
|
||||
:return:
|
||||
"""
|
||||
# Remove isStarting timer flag thingie
|
||||
self.isStarting = False
|
||||
|
||||
# Make sure we have enough players
|
||||
if self.countUsers() < 2 or not self.checkTeams():
|
||||
return
|
||||
return False
|
||||
|
||||
# Create playing channel
|
||||
glob.streams.add(self.playingStreamName)
|
||||
|
||||
# Change inProgress value
|
||||
match.inProgress = True
|
||||
self.inProgress = True
|
||||
|
||||
# Set playing to ready players and set load, skip and complete to False
|
||||
# Make clients join playing stream
|
||||
for i in range(0, 16):
|
||||
if (self.slots[i].status & slotStatuses.READY) > 0 and self.slots[i].user in glob.tokens.tokens:
|
||||
if self.slots[i].user in glob.tokens.tokens:
|
||||
self.slots[i].status = slotStatuses.PLAYING
|
||||
self.slots[i].loaded = False
|
||||
self.slots[i].skip = False
|
||||
|
@ -670,4 +804,86 @@ class match:
|
|||
glob.streams.broadcast(self.playingStreamName, serverPackets.matchStart(self.matchID))
|
||||
|
||||
# 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,3 +1,8 @@
|
|||
import threading
|
||||
import time
|
||||
|
||||
from common.sentry import sentry
|
||||
from constants.exceptions import periodicLoopException
|
||||
from objects import match
|
||||
from objects import glob
|
||||
from constants import serverPackets
|
||||
|
@ -9,7 +14,7 @@ class matchList:
|
|||
self.matches = {}
|
||||
self.lastID = 1
|
||||
|
||||
def createMatch(self, matchName, matchPassword, beatmapID, beatmapName, beatmapMD5, gameMode, hostUserID):
|
||||
def createMatch(self, matchName, matchPassword, beatmapID, beatmapName, beatmapMD5, gameMode, hostUserID, isTourney=False):
|
||||
"""
|
||||
Add a new match to matches list
|
||||
|
||||
|
@ -25,7 +30,7 @@ class matchList:
|
|||
# Add a new match to matches list and create its stream
|
||||
matchID = self.lastID
|
||||
self.lastID+=1
|
||||
self.matches[matchID] = match.match(matchID, matchName, matchPassword, beatmapID, beatmapName, beatmapMD5, gameMode, hostUserID)
|
||||
self.matches[matchID] = match.match(matchID, matchName, matchPassword, beatmapID, beatmapName, beatmapMD5, gameMode, hostUserID, isTourney)
|
||||
return matchID
|
||||
|
||||
def disposeMatch(self, matchID):
|
||||
|
@ -39,10 +44,69 @@ class matchList:
|
|||
if matchID not in self.matches:
|
||||
return
|
||||
|
||||
# Remove match object and stream
|
||||
match = self.matches.pop(matchID)
|
||||
glob.streams.remove(match.streamName)
|
||||
glob.streams.remove(match.playingStreamName)
|
||||
# Get match and disconnect all players
|
||||
_match = self.matches[matchID]
|
||||
for slot in _match.slots:
|
||||
_token = glob.tokens.getTokenFromUserID(slot.userID, ignoreIRC=True)
|
||||
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
|
||||
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,6 +5,7 @@ import uuid
|
|||
from common.constants import gameModes, actions
|
||||
from common.log import logUtils as log
|
||||
from common.ripple import userUtils
|
||||
from constants import exceptions
|
||||
from constants import serverPackets
|
||||
from events import logoutEvent
|
||||
from helpers import chatHelper as chat
|
||||
|
@ -29,16 +30,18 @@ class token:
|
|||
self.username = userUtils.getUsername(self.userID)
|
||||
self.safeUsername = userUtils.getSafeUsername(self.userID)
|
||||
self.privileges = userUtils.getPrivileges(self.userID)
|
||||
self.admin = userUtils.isInPrivilegeGroup(self.userID, "developer") or userUtils.isInPrivilegeGroup(self.userID, "community manager")
|
||||
self.admin = userUtils.isInPrivilegeGroup(self.userID, "developer")\
|
||||
or userUtils.isInPrivilegeGroup(self.userID, "community manager")\
|
||||
or userUtils.isInPrivilegeGroup(self.userID, "chat mod")
|
||||
self.irc = irc
|
||||
self.kicked = False
|
||||
self.restricted = userUtils.isRestricted(self.userID)
|
||||
self.loginTime = int(time.time())
|
||||
self.pingTime = self.loginTime
|
||||
self.timeOffset = timeOffset
|
||||
self.lock = threading.Lock() # Sync primitive
|
||||
self.streams = []
|
||||
self.tournament = tournament
|
||||
self.messagesBuffer = []
|
||||
|
||||
# Default variables
|
||||
self.spectators = []
|
||||
|
@ -82,6 +85,11 @@ class token:
|
|||
else:
|
||||
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
|
||||
self.updateCachedStats()
|
||||
|
||||
|
@ -96,41 +104,64 @@ class token:
|
|||
"""
|
||||
Add bytes (packets) to queue
|
||||
|
||||
:param bytes: (packet) bytes to enqueue
|
||||
:param bytes_: (packet) bytes to enqueue
|
||||
"""
|
||||
# TODO: reduce max queue size
|
||||
if len(bytes_) < 10 * 10 ** 6:
|
||||
self.queue += bytes_
|
||||
else:
|
||||
log.warning("{}'s packets buffer is above 10M!! Lost some data!".format(self.username))
|
||||
try:
|
||||
# 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:
|
||||
self.queue += bytes_
|
||||
else:
|
||||
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):
|
||||
"""Resets the queue. Call when enqueued packets have been sent"""
|
||||
self.queue = bytes()
|
||||
try:
|
||||
self._bufferLock.acquire()
|
||||
self.queue = bytes()
|
||||
finally:
|
||||
self._bufferLock.release()
|
||||
|
||||
def joinChannel(self, channel):
|
||||
def joinChannel(self, channelObject):
|
||||
"""
|
||||
Add channel to joined channels list
|
||||
Join a channel
|
||||
|
||||
:param channel: channel name
|
||||
:param channelObject: channel object
|
||||
:raises: exceptions.userAlreadyInChannelException()
|
||||
exceptions.channelNoPermissionsException()
|
||||
"""
|
||||
if channel not in self.joinedChannels:
|
||||
self.joinedChannels.append(channel)
|
||||
if channelObject.name in self.joinedChannels:
|
||||
raise exceptions.userAlreadyInChannelException()
|
||||
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, channel):
|
||||
def partChannel(self, channelObject):
|
||||
"""
|
||||
Remove channel from joined channels list
|
||||
|
||||
:param channel: channel name
|
||||
:param channelObject: channel object
|
||||
"""
|
||||
if channel in self.joinedChannels:
|
||||
self.joinedChannels.remove(channel)
|
||||
self.joinedChannels.remove(channelObject.name)
|
||||
self.leaveStream("chat/{}".format(channelObject.name))
|
||||
|
||||
def setLocation(self, latitude, longitude):
|
||||
"""
|
||||
Set client location
|
||||
|
||||
:param location: [latitude, longitude]
|
||||
:param latitude: latitude
|
||||
:param longitude: longitude
|
||||
"""
|
||||
self.location = (latitude, longitude)
|
||||
|
||||
|
@ -157,42 +188,47 @@ class token:
|
|||
|
||||
:param host: host osuToken object
|
||||
"""
|
||||
# Stop spectating old client
|
||||
self.stopSpectating()
|
||||
try:
|
||||
self._spectLock.acquire()
|
||||
|
||||
# Set new spectator host
|
||||
self.spectating = host.token
|
||||
self.spectatingUserID = host.userID
|
||||
# Stop spectating old client
|
||||
self.stopSpectating()
|
||||
|
||||
# Add us to host's spectator list
|
||||
host.spectators.append(self.token)
|
||||
# Set new spectator host
|
||||
self.spectating = host.token
|
||||
self.spectatingUserID = host.userID
|
||||
|
||||
# Create and join spectator stream
|
||||
streamName = "spect/{}".format(host.userID)
|
||||
glob.streams.add(streamName)
|
||||
self.joinStream(streamName)
|
||||
host.joinStream(streamName)
|
||||
# Add us to host's spectator list
|
||||
host.spectators.append(self.token)
|
||||
|
||||
# Send spectator join packet to host
|
||||
host.enqueue(serverPackets.addSpectator(self.userID))
|
||||
# Create and join spectator stream
|
||||
streamName = "spect/{}".format(host.userID)
|
||||
glob.streams.add(streamName)
|
||||
self.joinStream(streamName)
|
||||
host.joinStream(streamName)
|
||||
|
||||
# Create and join #spectator (#spect_userid) channel
|
||||
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))
|
||||
# Send spectator join packet to host
|
||||
host.enqueue(serverPackets.addSpectator(self.userID))
|
||||
|
||||
# Send fellow spectator join to all clients
|
||||
glob.streams.broadcast(streamName, serverPackets.fellowSpectatorJoined(self.userID))
|
||||
# Create and join #spectator (#spect_userid) channel
|
||||
glob.channels.addTempChannel("#spect_{}".format(host.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)
|
||||
|
||||
# Get current spectators list
|
||||
for i in host.spectators:
|
||||
if i != self.token and i in glob.tokens.tokens:
|
||||
self.enqueue(serverPackets.fellowSpectatorJoined(glob.tokens.tokens[i].userID))
|
||||
# Send fellow spectator join to all clients
|
||||
glob.streams.broadcast(streamName, serverPackets.fellowSpectatorJoined(self.userID))
|
||||
|
||||
# Log
|
||||
log.info("{} is spectating {}".format(self.username, host.username))
|
||||
# Get current spectators list
|
||||
for i in host.spectators:
|
||||
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):
|
||||
"""
|
||||
|
@ -201,43 +237,48 @@ class token:
|
|||
|
||||
:return:
|
||||
"""
|
||||
# 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)
|
||||
try:
|
||||
self._spectLock.acquire()
|
||||
|
||||
# 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))
|
||||
# Remove our userID from host's spectators
|
||||
if self.spectating is None or self.spectatingUserID <= 0:
|
||||
return
|
||||
if self.spectating in glob.tokens.tokens:
|
||||
hostToken = glob.tokens.tokens[self.spectating]
|
||||
else:
|
||||
hostToken = None
|
||||
streamName = "spect/{}".format(self.spectatingUserID)
|
||||
|
||||
# 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))
|
||||
# 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))
|
||||
|
||||
# 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)
|
||||
# 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))
|
||||
|
||||
# Part #spectator channel
|
||||
chat.partChannel(token=self, channel="#spect_{}".format(self.spectatingUserID), kick=True)
|
||||
# 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 {}".format(self.username, self.spectatingUserID))
|
||||
# Console output
|
||||
log.info("{} is no longer spectating {}. Current spectators: {}".format(self.username, self.spectatingUserID, hostToken.spectators))
|
||||
|
||||
# Set our spectating user to 0
|
||||
self.spectating = None
|
||||
self.spectatingUserID = 0
|
||||
# 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):
|
||||
"""
|
||||
|
@ -277,9 +318,16 @@ class token:
|
|||
# Set matchID, join stream, channel and send packet
|
||||
self.matchID = matchID
|
||||
self.joinStream(match.streamName)
|
||||
chat.joinChannel(token=self, channel="#multi_{}".format(self.matchID))
|
||||
chat.joinChannel(token=self, channel="#multi_{}".format(self.matchID), force=True)
|
||||
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):
|
||||
"""
|
||||
Leave joined match, match stream and match channel
|
||||
|
@ -291,27 +339,33 @@ class token:
|
|||
return
|
||||
|
||||
# Part #multiplayer channel and streams (/ and /playing)
|
||||
chat.partChannel(token=self, channel="#multi_{}".format(self.matchID), kick=True)
|
||||
chat.partChannel(token=self, channel="#multi_{}".format(self.matchID), kick=True, force=True)
|
||||
self.leaveStream("multi/{}".format(self.matchID))
|
||||
self.leaveStream("multi/{}/playing".format(self.matchID)) # optional
|
||||
|
||||
# Set usertoken match to -1
|
||||
leavingMatchID = self.matchID
|
||||
self.matchID = -1
|
||||
|
||||
# Make sure the match exists
|
||||
if self.matchID not in glob.matches.matches:
|
||||
if leavingMatchID not in glob.matches.matches:
|
||||
return
|
||||
|
||||
# The match exists, get object
|
||||
match = glob.matches.matches[self.matchID]
|
||||
match = glob.matches.matches[leavingMatchID]
|
||||
|
||||
# Set slot to free
|
||||
match.userLeft(self)
|
||||
|
||||
# Set usertoken match to -1
|
||||
self.matchID = -1
|
||||
if match.isTourney:
|
||||
# If an user leaves, then the ready status of the match changes and
|
||||
# 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"):
|
||||
"""
|
||||
Kick this user from the server
|
||||
|
||||
|
||||
:param message: Notification message to send to this user.
|
||||
Default: "You have been kicked from the server. Please login again."
|
||||
:param reason: Kick reason, used in logs. Default: "kick"
|
||||
|
@ -324,7 +378,7 @@ class token:
|
|||
self.enqueue(serverPackets.loginFailed())
|
||||
|
||||
# Logout event
|
||||
logoutEvent.handle(self, deleteToken=False)
|
||||
logoutEvent.handle(self, deleteToken=self.irc)
|
||||
|
||||
def silence(self, seconds = None, reason = "", author = 999):
|
||||
"""
|
||||
|
@ -433,7 +487,7 @@ class token:
|
|||
:return:
|
||||
"""
|
||||
self.restricted = True
|
||||
chat.sendMessage("FokaBot", self.username, "Your account is currently in restricted mode. Please visit ripple's website for more information.")
|
||||
chat.sendMessage(glob.BOT_NAME, self.username, "Your account is currently in restricted mode. Please visit ripple's website for more information.")
|
||||
|
||||
def resetRestricted(self):
|
||||
"""
|
||||
|
@ -442,7 +496,7 @@ class token:
|
|||
|
||||
:return:
|
||||
"""
|
||||
chat.sendMessage("FokaBot", self.username, "Your account has been unrestricted! Please log in again.")
|
||||
chat.sendMessage(glob.BOT_NAME, self.username, "Your account has been unrestricted! Please log in again.")
|
||||
|
||||
def joinStream(self, name):
|
||||
"""
|
||||
|
@ -486,4 +540,25 @@ class token:
|
|||
if self.awayMessage == "" or userID in self.sentAway:
|
||||
return False
|
||||
self.sentAway.append(userID)
|
||||
return True
|
||||
return True
|
||||
|
||||
def addMessageInBuffer(self, chan, message):
|
||||
"""
|
||||
Add a message in messages buffer (10 messages, truncated at 50 chars).
|
||||
Used as proof when the user gets reported.
|
||||
|
||||
:param chan: channel
|
||||
:param message: message content
|
||||
:return:
|
||||
"""
|
||||
if len(self.messagesBuffer) > 9:
|
||||
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,15 +43,29 @@ class stream:
|
|||
log.info("{} has left stream {}".format(token, self.name))
|
||||
self.clients.remove(token)
|
||||
|
||||
def broadcast(self, data):
|
||||
def broadcast(self, data, but=None):
|
||||
"""
|
||||
Send some data to all clients connected to this stream
|
||||
Send some data to all (or some) clients connected to this stream
|
||||
|
||||
:param data: data to send
|
||||
:param but: array of tokens to ignore. Default: None (send to everyone)
|
||||
:return:
|
||||
"""
|
||||
if but is None:
|
||||
but = []
|
||||
for i in self.clients:
|
||||
if i in glob.tokens.tokens:
|
||||
if i not in but:
|
||||
glob.tokens.tokens[i].enqueue(data)
|
||||
else:
|
||||
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].enqueue(data)
|
||||
else:
|
||||
self.removeClient(token=i)
|
||||
glob.tokens.tokens[i].leaveStream(self.name)
|
|
@ -1,6 +1,7 @@
|
|||
from objects import stream
|
||||
from objects import glob
|
||||
|
||||
# TODO: use *args and **kwargs
|
||||
class streamList:
|
||||
def __init__(self):
|
||||
self.streams = {}
|
||||
|
@ -55,25 +56,39 @@ class streamList:
|
|||
return
|
||||
self.streams[streamName].removeClient(client=client, token=token)
|
||||
|
||||
def broadcast(self, streamName, data):
|
||||
def broadcast(self, streamName, data, but=None):
|
||||
"""
|
||||
Send some data to all clients in a stream
|
||||
|
||||
:param streamName: stream name
|
||||
:param data: data to send
|
||||
:param but: array of tokens to ignore. Default: None (send to everyone)
|
||||
:return:
|
||||
"""
|
||||
if streamName not in self.streams:
|
||||
return
|
||||
self.streams[streamName].broadcast(data)
|
||||
self.streams[streamName].broadcast(data, but)
|
||||
|
||||
'''def getClients(self, streamName):
|
||||
def dispose(self, streamName, *args, **kwargs):
|
||||
"""
|
||||
Get all clients in a stream
|
||||
Call `dispose` on `streamName`
|
||||
|
||||
:param streamName: name of the stream
|
||||
:param args:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
if streamName not in self.streams:
|
||||
return
|
||||
return self.streams[streamName].clients'''
|
||||
self.streams[streamName].dispose(*args, **kwargs)
|
||||
|
||||
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,22 +1,35 @@
|
|||
import threading
|
||||
import time
|
||||
|
||||
import redis
|
||||
|
||||
from common.ripple import userUtils
|
||||
from common.log import logUtils as log
|
||||
from common.sentry import sentry
|
||||
from constants import serverPackets
|
||||
from constants.exceptions import periodicLoopException
|
||||
from events import logoutEvent
|
||||
from objects import glob
|
||||
from objects import osuToken
|
||||
|
||||
|
||||
class tokenList:
|
||||
def __init__(self):
|
||||
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):
|
||||
"""
|
||||
Add a token object to tokens list
|
||||
|
||||
:param userID: user id associated to that token
|
||||
:param ip: ip address of the client
|
||||
:param irc: if True, set this token as IRC client
|
||||
: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.
|
||||
|
@ -37,7 +50,8 @@ class tokenList:
|
|||
if token in self.tokens:
|
||||
if self.tokens[token].ip != "":
|
||||
userUtils.deleteBanchoSessions(self.tokens[token].userID, self.tokens[token].ip)
|
||||
self.tokens.pop(token)
|
||||
t = self.tokens.pop(token)
|
||||
del t
|
||||
glob.redis.decr("ripple:online_users")
|
||||
|
||||
def getUserIDFromToken(self, token):
|
||||
|
@ -54,25 +68,34 @@ class tokenList:
|
|||
# Get userID associated to that token
|
||||
return self.tokens[token].userID
|
||||
|
||||
def getTokenFromUserID(self, userID, ignoreIRC=False):
|
||||
def getTokenFromUserID(self, userID, ignoreIRC=False, _all=False):
|
||||
"""
|
||||
Get token from a user ID
|
||||
|
||||
:param userID: user ID to find
|
||||
:param ignoreIRC: if True, consider bancho clients only and skip IRC clients
|
||||
: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
|
||||
ret = []
|
||||
for _, value in self.tokens.items():
|
||||
if value.userID == userID:
|
||||
if ignoreIRC and value.irc:
|
||||
continue
|
||||
return value
|
||||
if _all:
|
||||
ret.append(value)
|
||||
else:
|
||||
return value
|
||||
|
||||
# Return none if not found
|
||||
return None
|
||||
# Return full list or None if not found
|
||||
if _all:
|
||||
return ret
|
||||
else:
|
||||
return None
|
||||
|
||||
def getTokenFromUsername(self, username, ignoreIRC=False, safe=False):
|
||||
def getTokenFromUsername(self, username, ignoreIRC=False, safe=False, _all=False):
|
||||
"""
|
||||
Get an osuToken object from an username
|
||||
|
||||
|
@ -80,20 +103,29 @@ class tokenList:
|
|||
:param ignoreIRC: if True, consider bancho clients only and skip IRC clients
|
||||
: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
|
||||
who = username.lower() if not safe else username
|
||||
|
||||
# Make sure the token exists
|
||||
ret = []
|
||||
for _, value in self.tokens.items():
|
||||
if (not safe and value.username.lower() == who) or (safe and value.safeUsername == who):
|
||||
if ignoreIRC and value.irc:
|
||||
continue
|
||||
return value
|
||||
if _all:
|
||||
ret.append(value)
|
||||
else:
|
||||
return value
|
||||
|
||||
# Return none if not found
|
||||
return None
|
||||
# Return full list or None if not found
|
||||
if _all:
|
||||
return ret
|
||||
else:
|
||||
return None
|
||||
|
||||
def deleteOldTokens(self, userID):
|
||||
"""
|
||||
|
@ -142,36 +174,49 @@ class tokenList:
|
|||
for _, value in self.tokens.items():
|
||||
value.enqueue(packet)
|
||||
|
||||
def usersTimeoutCheckLoop(self, timeoutTime = 100, checkTime = 100):
|
||||
@sentry.capture()
|
||||
def usersTimeoutCheckLoop(self):
|
||||
"""
|
||||
Start timed out users disconnect loop.
|
||||
This function will be called every `checkTime` seconds and so on, forever.
|
||||
CALL THIS FUNCTION ONLY ONCE!
|
||||
|
||||
:param timeoutTime: seconds of inactivity required to disconnect someone. Default: 100
|
||||
:param checkTime: seconds between loops. Default: 100
|
||||
:return:
|
||||
"""
|
||||
log.debug("Checking timed out clients")
|
||||
timedOutTokens = [] # timed out users
|
||||
timeoutLimit = int(time.time())-timeoutTime
|
||||
for key, value in self.tokens.items():
|
||||
# Check timeout (fokabot is ignored)
|
||||
if value.pingTime < timeoutLimit and value.userID != 999 and value.irc == False and value.tournament == False:
|
||||
# That user has timed out, add to disconnected tokens
|
||||
# We can't delete it while iterating or items() throws an error
|
||||
timedOutTokens.append(key)
|
||||
try:
|
||||
log.debug("Checking timed out clients")
|
||||
exceptions = []
|
||||
timedOutTokens = [] # timed out users
|
||||
timeoutLimit = int(time.time()) - 100
|
||||
for key, value in self.tokens.items():
|
||||
# Check timeout (fokabot is ignored)
|
||||
if value.pingTime < timeoutLimit and value.userID != 999 and not value.irc and not value.tournament:
|
||||
# That user has timed out, add to disconnected tokens
|
||||
# We can't delete it while iterating or items() throws an error
|
||||
timedOutTokens.append(key)
|
||||
|
||||
# Delete timed out users from self.tokens
|
||||
# i is token string (dictionary key)
|
||||
for i in timedOutTokens:
|
||||
log.debug("{} timed out!!".format(self.tokens[i].username))
|
||||
self.tokens[i].enqueue(serverPackets.notification("Your connection to the server timed out."))
|
||||
logoutEvent.handle(self.tokens[i], None)
|
||||
# Delete timed out users from self.tokens
|
||||
# i is token string (dictionary key)
|
||||
for i in timedOutTokens:
|
||||
log.debug("{} timed out!!".format(self.tokens[i].username))
|
||||
self.tokens[i].enqueue(serverPackets.notification("Your connection to the server timed out."))
|
||||
try:
|
||||
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
|
||||
|
||||
# Schedule a new check (endless loop)
|
||||
threading.Timer(checkTime, self.usersTimeoutCheckLoop, [timeoutTime, checkTime]).start()
|
||||
# Re-raise exceptions if needed
|
||||
if exceptions:
|
||||
raise periodicLoopException(exceptions)
|
||||
finally:
|
||||
# Schedule a new check (endless loop)
|
||||
threading.Timer(100, self.usersTimeoutCheckLoop).start()
|
||||
|
||||
@sentry.capture()
|
||||
def spamProtectionResetLoop(self):
|
||||
"""
|
||||
Start spam protection reset loop.
|
||||
|
@ -180,12 +225,13 @@ class tokenList:
|
|||
|
||||
:return:
|
||||
"""
|
||||
# Reset spamRate for every token
|
||||
for _, value in self.tokens.items():
|
||||
value.spamRate = 0
|
||||
|
||||
# Schedule a new check (endless loop)
|
||||
threading.Timer(10, self.spamProtectionResetLoop).start()
|
||||
try:
|
||||
# Reset spamRate for every token
|
||||
for _, value in self.tokens.items():
|
||||
value.spamRate = 0
|
||||
finally:
|
||||
# Schedule a new check (endless loop)
|
||||
threading.Timer(10, self.spamProtectionResetLoop).start()
|
||||
|
||||
def deleteBanchoSessions(self):
|
||||
"""
|
||||
|
@ -197,7 +243,7 @@ class tokenList:
|
|||
try:
|
||||
# TODO: Make function or some redis meme
|
||||
glob.redis.eval("return redis.call('del', unpack(redis.call('keys', ARGV[1])))", 0, "peppy:sessions:*")
|
||||
except:
|
||||
except redis.RedisError:
|
||||
pass
|
||||
|
||||
|
||||
|
|
77
pep.py
77
pep.py
|
@ -1,9 +1,7 @@
|
|||
"""Hello, pep.py here, ex-owner of ripple and prime minister of Ripwot."""
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
from multiprocessing.pool import ThreadPool
|
||||
|
||||
import tornado.gen
|
||||
import tornado.httpserver
|
||||
import tornado.ioloop
|
||||
|
@ -11,7 +9,11 @@ import tornado.web
|
|||
from raven.contrib.tornado import AsyncSentryClient
|
||||
import redis
|
||||
|
||||
from common import generalUtils
|
||||
import json
|
||||
import shutil
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
from common import generalUtils, agpl
|
||||
from common.constants import bcolors
|
||||
from common.db import dbConnector
|
||||
from common.ddog import datadogClient
|
||||
|
@ -38,6 +40,7 @@ from pubSubHandlers import changeUsernameHandler
|
|||
|
||||
from pubSubHandlers import disconnectHandler
|
||||
from pubSubHandlers import banHandler
|
||||
from pubSubHandlers import notificationHandler
|
||||
from pubSubHandlers import updateSilenceHandler
|
||||
from pubSubHandlers import updateStatsHandler
|
||||
|
||||
|
@ -54,7 +57,15 @@ def make_app():
|
|||
(r"/stress", heavyHandler.handler)
|
||||
])
|
||||
|
||||
|
||||
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:
|
||||
# Server start
|
||||
consoleHelper.printServerStartHeader(True)
|
||||
|
@ -78,6 +89,34 @@ if __name__ == "__main__":
|
|||
sys.exit()
|
||||
else:
|
||||
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
|
||||
consoleHelper.printNoNl("> Checking folders... ")
|
||||
|
@ -144,7 +183,7 @@ if __name__ == "__main__":
|
|||
consoleHelper.printNoNl("> Creating threads pool... ")
|
||||
glob.pool = ThreadPool(int(glob.conf.config["server"]["threads"]))
|
||||
consoleHelper.printDone()
|
||||
except:
|
||||
except ValueError:
|
||||
consoleHelper.printError()
|
||||
consoleHelper.printColored("[!] Error while creating threads pool. Please check your config.ini and run the server again", bcolors.RED)
|
||||
|
||||
|
@ -157,6 +196,11 @@ if __name__ == "__main__":
|
|||
consoleHelper.printColored("[!] Error while loading chat filters. Make sure there is a filters.txt file present", bcolors.RED)
|
||||
raise
|
||||
|
||||
# Start fokabot
|
||||
consoleHelper.printNoNl("> Connecting bot... ")
|
||||
fokabot.connect()
|
||||
consoleHelper.printDone()
|
||||
|
||||
# Initialize chat channels
|
||||
print("> Initializing chat channels... ")
|
||||
glob.channels.loadChannels()
|
||||
|
@ -168,11 +212,6 @@ if __name__ == "__main__":
|
|||
glob.streams.add("lobby")
|
||||
consoleHelper.printDone()
|
||||
|
||||
# Start fokabot
|
||||
consoleHelper.printNoNl("> Connecting FokaBot... ")
|
||||
fokabot.connect()
|
||||
consoleHelper.printDone()
|
||||
|
||||
# Initialize user timeout check loop
|
||||
consoleHelper.printNoNl("> Initializing user timeout check loop... ")
|
||||
glob.tokens.usersTimeoutCheckLoop()
|
||||
|
@ -183,6 +222,11 @@ if __name__ == "__main__":
|
|||
glob.tokens.spamProtectionResetLoop()
|
||||
consoleHelper.printDone()
|
||||
|
||||
# Initialize multiplayer cleanup loop
|
||||
consoleHelper.printNoNl("> Initializing multiplayer cleanup loop... ")
|
||||
glob.matches.cleanupLoop()
|
||||
consoleHelper.printDone()
|
||||
|
||||
# Localize warning
|
||||
glob.localize = generalUtils.stringToBool(glob.conf.config["localize"]["enable"])
|
||||
if not glob.localize:
|
||||
|
@ -190,7 +234,7 @@ if __name__ == "__main__":
|
|||
|
||||
# Discord
|
||||
if generalUtils.stringToBool(glob.conf.config["discord"]["enable"]):
|
||||
glob.schiavo = schiavo.schiavo(glob.conf.config["discord"]["boturl"])
|
||||
glob.schiavo = schiavo.schiavo(glob.conf.config["discord"]["boturl"], "**pep.py**")
|
||||
else:
|
||||
consoleHelper.printColored("[!] Warning! Discord logging is disabled!", bcolors.YELLOW)
|
||||
|
||||
|
@ -214,7 +258,7 @@ if __name__ == "__main__":
|
|||
try:
|
||||
glob.sentry = generalUtils.stringToBool(glob.conf.config["sentry"]["enable"])
|
||||
if glob.sentry:
|
||||
glob.application.sentry_client = AsyncSentryClient(glob.conf.config["sentry"]["banchodns"], release=glob.VERSION)
|
||||
glob.application.sentry_client = AsyncSentryClient(glob.conf.config["sentry"]["banchodsn"], release=glob.VERSION)
|
||||
else:
|
||||
consoleHelper.printColored("[!] Warning! Sentry logging is disabled!", bcolors.YELLOW)
|
||||
except:
|
||||
|
@ -250,24 +294,26 @@ if __name__ == "__main__":
|
|||
glob.irc = generalUtils.stringToBool(glob.conf.config["irc"]["enable"])
|
||||
if glob.irc:
|
||||
# IRC port
|
||||
ircPort = 0
|
||||
try:
|
||||
ircPort = int(glob.conf.config["irc"]["port"])
|
||||
except:
|
||||
except ValueError:
|
||||
consoleHelper.printColored("[!] Invalid IRC port! Please check your config.ini and run the server again", bcolors.RED)
|
||||
log.logMessage("**pep.py** IRC server started!", discord="bunker", of="info.txt", stdout=False)
|
||||
log.logMessage("IRC server started!", discord="bunker", of="info.txt", stdout=False)
|
||||
consoleHelper.printColored("> IRC server listening on 127.0.0.1:{}...".format(ircPort), bcolors.GREEN)
|
||||
threading.Thread(target=lambda: ircserver.main(port=ircPort)).start()
|
||||
else:
|
||||
consoleHelper.printColored("[!] Warning! IRC server is disabled!", bcolors.YELLOW)
|
||||
|
||||
# Server port
|
||||
serverPort = 0
|
||||
try:
|
||||
serverPort = int(glob.conf.config["server"]["port"])
|
||||
except:
|
||||
except ValueError:
|
||||
consoleHelper.printColored("[!] Invalid server port! Please check your config.ini and run the server again", bcolors.RED)
|
||||
|
||||
# Server start message and console output
|
||||
log.logMessage("**pep.py** Server started!", discord="bunker", of="info.txt", stdout=False)
|
||||
log.logMessage("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)
|
||||
|
||||
# Connect to pubsub channels
|
||||
|
@ -278,6 +324,7 @@ if __name__ == "__main__":
|
|||
"peppy:update_cached_stats": updateStatsHandler.handler(),
|
||||
"peppy:silence": updateSilenceHandler.handler(),
|
||||
"peppy:ban": banHandler.handler(),
|
||||
"peppy:notification": notificationHandler.handler(),
|
||||
}).start()
|
||||
|
||||
# Start tornado
|
||||
|
|
|
@ -6,6 +6,7 @@ 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")
|
||||
|
|
19
pubSubHandlers/notificationHandler.py
Normal file
19
pubSubHandlers/notificationHandler.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
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,9 +1,10 @@
|
|||
requests
|
||||
tornado
|
||||
mysqlclient
|
||||
psutil
|
||||
raven
|
||||
bcrypt>=3.1.1
|
||||
dill
|
||||
redis
|
||||
cython
|
||||
requests==2.18.1
|
||||
tornado==4.4.2
|
||||
mysqlclient==1.3.9
|
||||
psutil==5.2.2
|
||||
raven==5.32.0
|
||||
bcrypt==3.1.1
|
||||
dill==0.2.7.1
|
||||
redis==2.10.5
|
||||
cython==0.27.3
|
||||
datadog==0.14.0
|
2
setup.py
2
setup.py
|
@ -7,7 +7,7 @@ import os
|
|||
cythonExt = []
|
||||
for root, dirs, files in os.walk(os.getcwd()):
|
||||
for file in files:
|
||||
if file.endswith(".pyx"):
|
||||
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]))
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user