Compare commits
72 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
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 | ||
|
e92cbe47bd | ||
|
6ca2016f7b | ||
|
2f54a56b7a | ||
|
cf9e506875 | ||
|
5c93d692ea | ||
|
a8a1dfb1bc | ||
|
9d562e7acd | ||
|
4f4253afce | ||
|
04898c24ae | ||
|
1b94936092 | ||
|
d4591b42a3 | ||
|
69508f9a0e | ||
|
5cf8c1bde8 | ||
|
20be60d9db | ||
|
61935f323c | ||
|
cecef18d13 | ||
|
5723c0e68f | ||
|
525235a27e | ||
|
3bc390e3e6 | ||
|
f6ae673401 | ||
|
aa32e8bea6 | ||
|
fb00063e0f | ||
|
e30893d66f | ||
|
8078596a0a | ||
|
eefec1f47b | ||
|
2992dc6190 | ||
|
b6e2319e8c | ||
|
a2ef03c887 | ||
|
abad698fe3 | ||
|
030d556b9c | ||
|
d51b304fbe | ||
|
1ecc73e0dc | ||
|
b1315815b2 | ||
|
38bcf3a735 | ||
|
a6292c7374 | ||
|
ef940771d8 | ||
|
b03d51abff | ||
|
8ef02faf36 | ||
|
d29dcd25f7 | ||
|
cd75d1ad8d | ||
|
c7c5528588 | ||
|
3b150d70cd | ||
|
d249dd593f | ||
|
78a6931805 | ||
|
555c9cca1f | ||
|
83c514b75e |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,8 +1,9 @@
|
||||
**/__pycache__
|
||||
**/build
|
||||
config.ini
|
||||
filters.txt
|
||||
.data
|
||||
.idea
|
||||
common_funzia
|
||||
common_refractor
|
||||
common_memato
|
||||
redistest.py
|
||||
*.c
|
||||
*.so
|
7
.landscape.yaml
Normal file
7
.landscape.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
python-targets:
|
||||
- 3
|
||||
pep8:
|
||||
none: true
|
||||
pylint:
|
||||
disable:
|
||||
- cyclic-import
|
15
README.md
15
README.md
@@ -1,4 +1,8 @@
|
||||
## pep.py
|
||||
## pep.py [](https://landscape.io/github/osuripple/pep.py/master)
|
||||
|
||||
- Origin: https://git.zxq.co/ripple/pep.py
|
||||
- Mirror: https://github.com/osuripple/pep.py
|
||||
|
||||
This is Ripple's bancho server. It handles:
|
||||
- Client login
|
||||
- Online users listing and statuses
|
||||
@@ -9,6 +13,8 @@ This is Ripple's bancho server. It handles:
|
||||
|
||||
## Requirements
|
||||
- Python 3.5
|
||||
- Cython
|
||||
- C compiler
|
||||
- MySQLdb (`mysqlclient`)
|
||||
- Tornado
|
||||
- Bcrypt
|
||||
@@ -23,9 +29,14 @@ afterwards, install the required dependencies with pip
|
||||
```
|
||||
$ pip install -r requirements.txt
|
||||
```
|
||||
then, run pep.py once to create the default config file and edit it
|
||||
then, compile all `*.pyx` files to `*.so` or `*.dll` files using `setup.py` (distutils file)
|
||||
```
|
||||
$ python3 setup.py build_ext --inplace
|
||||
```
|
||||
finally, run pep.py once to create the default config file and edit it
|
||||
```
|
||||
$ python3 pep.py
|
||||
...
|
||||
$ nano config.ini
|
||||
```
|
||||
you can run pep.py by typing
|
||||
|
2
common
2
common
Submodule common updated: 97ed79d4a1...6329b9ac2d
@@ -1,4 +1,3 @@
|
||||
""" Contains functions used to read specific client packets from byte stream """
|
||||
from constants import dataTypes
|
||||
from helpers import packetHelper
|
||||
from constants import slotStatuses
|
||||
@@ -100,7 +99,7 @@ def matchSettings(stream):
|
||||
start += 2
|
||||
for i in range(0,16):
|
||||
s = data[0]["slot{}Status".format(str(i))]
|
||||
if s != slotStatuses.free and s != slotStatuses.locked:
|
||||
if s != slotStatuses.FREE and s != slotStatuses.LOCKED:
|
||||
start += 4
|
||||
|
||||
# Other settings
|
||||
|
@@ -1,5 +1,3 @@
|
||||
"""Bancho exceptions"""
|
||||
# TODO: Prints in exceptions
|
||||
class loginFailedException(Exception):
|
||||
pass
|
||||
|
||||
@@ -92,3 +90,15 @@ class unknownStreamException(Exception):
|
||||
|
||||
class userTournamentException(Exception):
|
||||
pass
|
||||
|
||||
class userAlreadyInChannelException(Exception):
|
||||
pass
|
||||
|
||||
class userNotInChannelException(Exception):
|
||||
pass
|
||||
|
||||
class missingReportInfoException(Exception):
|
||||
pass
|
||||
|
||||
class invalidUserException(Exception):
|
||||
pass
|
@@ -1,7 +1,9 @@
|
||||
import json
|
||||
import random
|
||||
import re
|
||||
|
||||
import requests
|
||||
import time
|
||||
|
||||
from common import generalUtils
|
||||
from common.constants import mods
|
||||
@@ -14,14 +16,15 @@ from constants import serverPackets
|
||||
from helpers import systemHelper
|
||||
from objects import fokabot
|
||||
from objects import glob
|
||||
from helpers import chatHelper as chat
|
||||
|
||||
"""
|
||||
Commands callbacks
|
||||
|
||||
Must have fro, chan and messages as arguments
|
||||
fro -- name of who triggered the command
|
||||
chan -- channel where the message was sent
|
||||
message -- list containing arguments passed from the message
|
||||
:param fro: username of who triggered the command
|
||||
:param chan: channel"(or username, if PM) where the message was sent
|
||||
:param message: list containing arguments passed from the message
|
||||
[0] = first argument
|
||||
[1] = second argument
|
||||
. . .
|
||||
@@ -35,6 +38,7 @@ def instantRestart(fro, chan, message):
|
||||
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":
|
||||
@@ -118,15 +122,18 @@ def kickAll(fro, chan, message):
|
||||
|
||||
def kick(fro, chan, message):
|
||||
# Get parameters
|
||||
target = message[0].replace("_", " ")
|
||||
target = message[0].lower().replace("_", " ")
|
||||
if target == "fokabot":
|
||||
return "Nope."
|
||||
|
||||
# Get target token and make sure is connected
|
||||
targetToken = glob.tokens.getTokenFromUsername(target)
|
||||
if targetToken is None:
|
||||
tokens = glob.tokens.getTokenFromUsername(target, _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)
|
||||
@@ -303,22 +310,7 @@ def systemShutdown(fro, chan, message):
|
||||
return restartShutdown(False)
|
||||
|
||||
def systemReload(fro, chan, message):
|
||||
# Reload settings from bancho_settings
|
||||
glob.banchoConf.loadSettings()
|
||||
|
||||
# Reload channels too
|
||||
glob.channels.loadChannels()
|
||||
|
||||
# And chat filters
|
||||
glob.chatFilters.loadFilters()
|
||||
|
||||
# Send new channels and new bottom icon to everyone
|
||||
glob.streams.broadcast("main", serverPackets.mainMenuIcon(glob.banchoConf.config["menuIcon"]))
|
||||
glob.streams.broadcast("main", serverPackets.channelInfoEnd())
|
||||
for key, value in glob.channels.channels.items():
|
||||
if value.publicRead == True and value.hidden == False:
|
||||
glob.streams.broadcast("main", serverPackets.channelInfo(key))
|
||||
|
||||
glob.banchoConf.reload()
|
||||
return "Bancho settings reloaded!"
|
||||
|
||||
def systemMaintenance(fro, chan, message):
|
||||
@@ -359,7 +351,13 @@ def systemStatus(fro, chan, message):
|
||||
data = systemHelper.getSystemInfo()
|
||||
|
||||
# Final message
|
||||
letsVersion = glob.redis.get("lets:version")
|
||||
if letsVersion is None:
|
||||
letsVersion = "\_(xd)_/"
|
||||
else:
|
||||
letsVersion = letsVersion.decode("utf-8")
|
||||
msg = "pep.py bancho server v{}\n".format(glob.VERSION)
|
||||
msg += "LETS scores server v{}\n".format(letsVersion)
|
||||
msg += "made by the Ripple team\n"
|
||||
msg += "\n"
|
||||
msg += "=== BANCHO STATS ===\n"
|
||||
@@ -388,7 +386,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
|
||||
@@ -664,7 +662,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("#"):
|
||||
@@ -699,6 +697,69 @@ def updateBeatmap(fro, chan, to):
|
||||
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() == "fokabot":
|
||||
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(target)
|
||||
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("FokaBot", "#admin", adminMsg)
|
||||
log.warning(adminMsg, discord="cm")
|
||||
except exceptions.invalidUserException:
|
||||
msg = "Hello, FokaBot here! You can't report me. I won't forget what you've tried to do. Watch out."
|
||||
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("FokaBot", fro, msg)
|
||||
else:
|
||||
token.enqueue(serverPackets.notification(msg))
|
||||
return False
|
||||
|
||||
"""
|
||||
Commands list
|
||||
|
||||
@@ -707,11 +768,6 @@ callback: function to call when the command is triggered. Optional.
|
||||
response: text to return when the command is triggered. Optional.
|
||||
syntax: command syntax. Arguments must be separated by spaces (eg: <arg1> <arg2>)
|
||||
privileges: privileges needed to execute the command. Optional.
|
||||
|
||||
NOTES:
|
||||
- You CAN'T use both rank and minRank at the same time.
|
||||
- If both rank and minrank are **not** present, everyone will be able to run that command.
|
||||
- You MUST set trigger and callback/response, or the command won't work.
|
||||
"""
|
||||
commands = [
|
||||
{
|
||||
@@ -723,7 +779,7 @@ 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"
|
||||
@@ -751,7 +807,7 @@ commands = [
|
||||
"callback": moderated
|
||||
}, {
|
||||
"trigger": "!kickall",
|
||||
"privileges": privileges.ADMIN_KICK_USERS,
|
||||
"privileges": privileges.ADMIN_MANAGE_SERVERS,
|
||||
"callback": kickAll
|
||||
}, {
|
||||
"trigger": "!kick",
|
||||
|
@@ -1,2 +1,2 @@
|
||||
normal = 0
|
||||
freeMod = 1
|
||||
NORMAL = 0
|
||||
FREE_MOD = 1
|
||||
|
@@ -1,3 +1,3 @@
|
||||
score = 0
|
||||
accuracy = 1
|
||||
combo = 2
|
||||
SCORE = 0
|
||||
ACCURACY = 1
|
||||
COMBO = 2
|
||||
|
@@ -1,4 +1,4 @@
|
||||
headToHead = 0
|
||||
tagCoop = 1
|
||||
teamVs = 2
|
||||
tagTeamVs = 3
|
||||
HEAD_TO_HEAD = 0
|
||||
TAG_COOP = 1
|
||||
TEAM_VS = 2
|
||||
TAG_TEAM_VS = 3
|
||||
|
@@ -1,3 +1,3 @@
|
||||
noTeam = 0
|
||||
blue = 1
|
||||
red = 2
|
||||
NO_TEAM = 0
|
||||
BLUE = 1
|
||||
RED = 2
|
||||
|
@@ -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],
|
||||
[channel.getConnectedUsersCount(), dataTypes.UINT16]
|
||||
[len(glob.streams.streams["chat/{}".format(chan)].clients), dataTypes.UINT16]
|
||||
])
|
||||
|
||||
def channelInfoEnd():
|
||||
|
@@ -1,8 +1,8 @@
|
||||
free = 1
|
||||
locked = 2
|
||||
notReady = 4
|
||||
ready = 8
|
||||
noMap = 16
|
||||
playing = 32
|
||||
occupied = 124
|
||||
playingQuit = 128
|
||||
FREE = 1
|
||||
LOCKED = 2
|
||||
NOT_READY = 4
|
||||
READY = 8
|
||||
NO_MAP = 16
|
||||
PLAYING = 32
|
||||
OCCUPIED = 124
|
||||
PLAYING_QUIT = 128
|
||||
|
@@ -1,4 +1,3 @@
|
||||
"""Bancho user ranks"""
|
||||
NORMAL = 0
|
||||
PLAYER = 1
|
||||
BAT = 2
|
||||
|
@@ -1,25 +1,21 @@
|
||||
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
|
||||
|
||||
|
||||
def handle(userToken, packetData):
|
||||
# Get usertoken data
|
||||
userID = userToken.userID
|
||||
username = userToken.username
|
||||
|
||||
# Make sure we are not banned
|
||||
if userUtils.isBanned(userID):
|
||||
userToken.enqueue(serverPackets.loginBanned())
|
||||
return
|
||||
#if userUtils.isBanned(userID):
|
||||
# userToken.enqueue(serverPackets.loginBanned())
|
||||
# return
|
||||
|
||||
# Send restricted message if needed
|
||||
if not userToken.restricted:
|
||||
if userUtils.isRestricted(userID):
|
||||
userToken.setRestricted()
|
||||
#if userToken.restricted:
|
||||
# userToken.checkRestricted(True)
|
||||
|
||||
# Change action packet
|
||||
packetData = clientPackets.userActionChange(packetData)
|
||||
@@ -35,8 +31,10 @@ if userToken.matchID != -1 and userToken.actionID != actions.MULTIPLAYING and us
|
||||
'''
|
||||
|
||||
# Update cached stats if our pp changed if we've just submitted a score or we've changed gameMode
|
||||
if (userToken.actionID == actions.PLAYING or userToken.actionID == actions.MULTIPLAYING) or (userToken.pp != userUtils.getPP(userID, userToken.gameMode)) or (userToken.gameMode != packetData["gameMode"]):
|
||||
# Always update game mode, or we'll cache stats from the wrong game mode if we've changed it
|
||||
#if (userToken.actionID == actions.PLAYING or userToken.actionID == actions.MULTIPLAYING) or (userToken.pp != userUtils.getPP(userID, userToken.gameMode)) or (userToken.gameMode != packetData["gameMode"]):
|
||||
|
||||
# Update cached stats if we've changed gamemode
|
||||
if userToken.gameMode != packetData["gameMode"]:
|
||||
userToken.gameMode = packetData["gameMode"]
|
||||
userToken.updateCachedStats()
|
||||
|
||||
|
@@ -18,7 +18,7 @@ def handle(userToken, packetData):
|
||||
match = glob.matches.matches[matchID]
|
||||
|
||||
# Set slot or match mods according to modType
|
||||
if match.matchModMode == matchModModes.freeMod:
|
||||
if match.matchModMode == matchModModes.FREE_MOD:
|
||||
# Freemod
|
||||
# Host can set global DT/HT
|
||||
if userID == match.hostUserID:
|
||||
|
@@ -81,11 +81,11 @@ def handle(userToken, packetData):
|
||||
# Reset ready if needed
|
||||
if oldMods != match.mods or oldBeatmapMD5 != match.beatmapMD5:
|
||||
for i in range(0,16):
|
||||
if match.slots[i].status == slotStatuses.ready:
|
||||
match.slots[i].status = slotStatuses.notReady
|
||||
if match.slots[i].status == slotStatuses.READY:
|
||||
match.slots[i].status = slotStatuses.NOT_READY
|
||||
|
||||
# Reset mods if needed
|
||||
if match.matchModMode == matchModModes.normal:
|
||||
if match.matchModMode == matchModModes.NORMAL:
|
||||
# Reset slot mods if not freeMods
|
||||
for i in range(0,16):
|
||||
match.slots[i].mods = 0
|
||||
@@ -94,21 +94,21 @@ def handle(userToken, packetData):
|
||||
match.mods = 0
|
||||
|
||||
# Set/reset teams
|
||||
if match.matchTeamType == matchTeamTypes.teamVs or match.matchTeamType == matchTeamTypes.tagTeamVs:
|
||||
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.noTeam:
|
||||
match.slots[i].team = matchTeams.red if c % 2 == 0 else matchTeams.blue
|
||||
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.noTeam
|
||||
match.slots[i].team = matchTeams.NO_TEAM
|
||||
|
||||
# Force no freemods if tag coop
|
||||
if match.matchTeamType == matchTeamTypes.tagCoop or match.matchTeamType == matchTeamTypes.tagTeamVs:
|
||||
match.matchModMode = matchModModes.normal
|
||||
if match.matchTeamType == matchTeamTypes.TAG_COOP or match.matchTeamType == matchTeamTypes.TAG_TEAM_VS:
|
||||
match.matchModMode = matchModModes.NORMAL
|
||||
|
||||
# Send updated settings
|
||||
match.sendUpdates()
|
||||
|
@@ -1,7 +1,6 @@
|
||||
from common.log import logUtils as log
|
||||
from constants import clientPackets
|
||||
from constants import exceptions
|
||||
from constants import serverPackets
|
||||
from objects import glob
|
||||
|
||||
|
||||
|
@@ -1,4 +1,3 @@
|
||||
from common import generalUtils
|
||||
from common.log import logUtils as log
|
||||
from constants import clientPackets
|
||||
from constants import exceptions
|
||||
|
@@ -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()
|
||||
@@ -46,7 +44,6 @@ def handle(tornadoRequest):
|
||||
splitData = loginData[2].split("|")
|
||||
osuVersion = splitData[0]
|
||||
timeOffset = int(splitData[1])
|
||||
print(str(timeOffset))
|
||||
clientData = splitData[3].split(":")[:5]
|
||||
if len(clientData) < 4:
|
||||
raise exceptions.forceUpdateException()
|
||||
@@ -192,19 +189,20 @@ def handle(tornadoRequest):
|
||||
# Get location and country from ip.zxq.co or database
|
||||
if glob.localize:
|
||||
# Get location and country from IP
|
||||
location = locationHelper.getLocation(requestIP)
|
||||
latitude, longitude = locationHelper.getLocation(requestIP)
|
||||
countryLetters = locationHelper.getCountry(requestIP)
|
||||
country = countryHelper.getCountryID(countryLetters)
|
||||
else:
|
||||
# Set location to 0,0 and get country from db
|
||||
log.warning("Location skipped")
|
||||
location = [0,0]
|
||||
latitude = 0
|
||||
longitude = 0
|
||||
countryLetters = "XX"
|
||||
country = countryHelper.getCountryID(userUtils.getCountry(userID))
|
||||
|
||||
# Set location and country
|
||||
responseToken.setLocation(location)
|
||||
responseToken.setCountry(country)
|
||||
responseToken.setLocation(latitude, longitude)
|
||||
responseToken.country = country
|
||||
|
||||
# Set country in db if user has no country (first bancho login)
|
||||
if userUtils.getCountry(userID) == "XX":
|
||||
@@ -220,24 +218,22 @@ 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 = 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()
|
||||
@@ -251,7 +247,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 +254,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
|
@@ -1,4 +1,5 @@
|
||||
import time
|
||||
import json
|
||||
|
||||
from common.log import logUtils as log
|
||||
from constants import serverPackets
|
||||
@@ -6,7 +7,7 @@ from helpers import chatHelper as chat
|
||||
from objects import glob
|
||||
|
||||
|
||||
def handle(userToken, _=None):
|
||||
def handle(userToken, _=None, deleteToken=True):
|
||||
# get usertoken data
|
||||
userID = userToken.userID
|
||||
username = userToken.username
|
||||
@@ -16,7 +17,7 @@ def handle(userToken, _=None):
|
||||
# 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()
|
||||
|
||||
@@ -34,11 +35,23 @@ def handle(userToken, _=None):
|
||||
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
|
||||
if deleteToken:
|
||||
glob.tokens.deleteToken(requestToken)
|
||||
else:
|
||||
userToken.kicked = True
|
||||
|
||||
# Change username if needed
|
||||
newUsername = glob.redis.get("ripple:change_username_pending:{}".format(userID))
|
||||
if newUsername is not None:
|
||||
log.debug("Sending username change request for user {}".format(userID))
|
||||
glob.redis.publish("peppy:change_username", json.dumps({
|
||||
"userID": userID,
|
||||
"newUsername": newUsername.decode("utf-8")
|
||||
}))
|
||||
|
||||
# Console output
|
||||
log.info("{} has been disconnected. (logout)".format(username))
|
||||
|
@@ -1,6 +1,6 @@
|
||||
from objects import glob
|
||||
|
||||
def handle(userToken, packetData, has):
|
||||
def handle(userToken, _, has):
|
||||
# Get usertoken data
|
||||
userID = userToken.userID
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
from objects import glob
|
||||
|
||||
def handle(userToken, packetData):
|
||||
def handle(userToken, _):
|
||||
# Get usertoken data
|
||||
userID = userToken.userID
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
from objects import glob
|
||||
from constants import slotStatuses
|
||||
from constants import serverPackets
|
||||
|
||||
def handle(userToken, packetData):
|
||||
|
@@ -1,6 +1,6 @@
|
||||
from objects import glob
|
||||
|
||||
def handle(userToken, packetData):
|
||||
def handle(userToken, _):
|
||||
# Get userToken data
|
||||
userID = userToken.userID
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
from objects import glob
|
||||
|
||||
def handle(userToken, packetData):
|
||||
def handle(userToken, _):
|
||||
# Get userToken data
|
||||
userID = userToken.userID
|
||||
|
||||
|
@@ -1,6 +1,4 @@
|
||||
from objects import glob
|
||||
from constants import slotStatuses
|
||||
from constants import serverPackets
|
||||
|
||||
def handle(userToken, _):
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -11,7 +11,7 @@ def handle(userToken, packetData):
|
||||
packetData = clientPackets.setAwayMessage(packetData)
|
||||
|
||||
# Set token away message
|
||||
userToken.setAwayMessage(packetData["awayMessage"])
|
||||
userToken.awayMessage = packetData["awayMessage"]
|
||||
|
||||
# Send private message from fokabot
|
||||
if packetData["awayMessage"] == "":
|
||||
|
@@ -1,6 +1,5 @@
|
||||
from objects import glob
|
||||
from constants import serverPackets
|
||||
from constants import exceptions
|
||||
|
||||
def handle(userToken, packetData):
|
||||
# get token data
|
||||
|
@@ -1,5 +1,4 @@
|
||||
from constants import clientPackets
|
||||
from constants import serverPackets
|
||||
from objects import glob
|
||||
|
||||
def handle(userToken, packetData):
|
||||
|
4
full_build.sh
Normal file
4
full_build.sh
Normal file
@@ -0,0 +1,4 @@
|
||||
find . -name "*.c" -type f -delete
|
||||
find . -name "*.o" -type f -delete
|
||||
find . -name "*.so" -type f -delete
|
||||
python3 setup.py build_ext --inplace
|
@@ -1,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,6 @@ class handler(requestsManager.asyncRequestHandler):
|
||||
if key is None or key != glob.conf.config["server"]["cikey"]:
|
||||
raise exceptions.invalidArgumentsException()
|
||||
|
||||
log.info("API REQUEST FOR FOKABOT MESSAGE AAAAAAA")
|
||||
chatHelper.sendMessage("FokaBot", self.get_argument("to"), self.get_argument("msg"))
|
||||
|
||||
# Status code and message
|
||||
@@ -35,7 +40,5 @@ class handler(requestsManager.asyncRequestHandler):
|
||||
data["status"] = statusCode
|
||||
|
||||
# Send response
|
||||
#self.clear()
|
||||
self.write(json.dumps(data))
|
||||
self.set_status(statusCode)
|
||||
#self.finish(json.dumps(data))
|
||||
|
@@ -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")
|
||||
#username = self.get_argument("u").lower().replace(" ", "_")
|
||||
username = userUtils.safeUsername(self.get_argument("u"))
|
||||
else:
|
||||
try:
|
||||
userID = int(self.get_argument("id"))
|
||||
@@ -29,7 +38,7 @@ class handler(requestsManager.asyncRequestHandler):
|
||||
data["result"] = False
|
||||
else:
|
||||
if username is not None:
|
||||
data["result"] = True if glob.tokens.getTokenFromUsername(username) is not None else False
|
||||
data["result"] = True if glob.tokens.getTokenFromUsername(username, safe=True) is not None else False
|
||||
else:
|
||||
data["result"] = True if glob.tokens.getTokenFromUserID(userID) is not None else False
|
||||
|
||||
@@ -44,7 +53,5 @@ class handler(requestsManager.asyncRequestHandler):
|
||||
data["status"] = statusCode
|
||||
|
||||
# Send response
|
||||
#self.clear()
|
||||
self.write(json.dumps(data))
|
||||
self.set_status(statusCode)
|
||||
#self.finish(json.dumps(data))
|
||||
|
@@ -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"] = len(glob.tokens.tokens)
|
||||
data["result"] = int(glob.redis.get("ripple:online_users").decode("utf-8"))
|
||||
|
||||
# Status code and message
|
||||
statusCode = 200
|
||||
@@ -20,7 +27,5 @@ class handler(requestsManager.asyncRequestHandler):
|
||||
data["status"] = statusCode
|
||||
|
||||
# Send response
|
||||
#self.clear()
|
||||
self.write(json.dumps(data))
|
||||
self.set_status(statusCode)
|
||||
#self.finish(json.dumps(data))
|
||||
|
@@ -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"}
|
||||
@@ -20,7 +27,5 @@ class handler(requestsManager.asyncRequestHandler):
|
||||
data["status"] = statusCode
|
||||
|
||||
# Send response
|
||||
#self.clear()
|
||||
self.write(json.dumps(data))
|
||||
self.set_status(statusCode)
|
||||
#self.finish(json.dumps(data))
|
||||
|
@@ -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"}
|
||||
|
@@ -1,270 +0,0 @@
|
||||
import datetime
|
||||
import gzip
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import tornado.gen
|
||||
import tornado.web
|
||||
from raven.contrib.tornado import SentryMixin
|
||||
|
||||
from common.log import logUtils as log
|
||||
from common.web import requestsManager
|
||||
from constants import exceptions
|
||||
from constants import packetIDs
|
||||
from constants import serverPackets
|
||||
from events import cantSpectateEvent
|
||||
from events import changeActionEvent
|
||||
from events import changeMatchModsEvent
|
||||
from events import changeMatchPasswordEvent
|
||||
from events import changeMatchSettingsEvent
|
||||
from events import changeSlotEvent
|
||||
from events import channelJoinEvent
|
||||
from events import channelPartEvent
|
||||
from events import createMatchEvent
|
||||
from events import friendAddEvent
|
||||
from events import friendRemoveEvent
|
||||
from events import joinLobbyEvent
|
||||
from events import joinMatchEvent
|
||||
from events import loginEvent
|
||||
from events import logoutEvent
|
||||
from events import matchChangeTeamEvent
|
||||
from events import matchCompleteEvent
|
||||
from events import matchFailedEvent
|
||||
from events import matchFramesEvent
|
||||
from events import matchHasBeatmapEvent
|
||||
from events import matchInviteEvent
|
||||
from events import matchLockEvent
|
||||
from events import matchNoBeatmapEvent
|
||||
from events import matchPlayerLoadEvent
|
||||
from events import matchReadyEvent
|
||||
from events import matchSkipEvent
|
||||
from events import matchStartEvent
|
||||
from events import matchTransferHostEvent
|
||||
from events import partLobbyEvent
|
||||
from events import partMatchEvent
|
||||
from events import requestStatusUpdateEvent
|
||||
from events import sendPrivateMessageEvent
|
||||
from events import sendPublicMessageEvent
|
||||
from events import setAwayMessageEvent
|
||||
from events import spectateFramesEvent
|
||||
from events import startSpectatingEvent
|
||||
from events import stopSpectatingEvent
|
||||
from events import userPanelRequestEvent
|
||||
from events import userStatsRequestEvent
|
||||
from events import tournamentMatchInfoRequestEvent
|
||||
from events import tournamentJoinMatchChannelEvent
|
||||
from events import tournamentLeaveMatchChannelEvent
|
||||
from helpers import packetHelper
|
||||
from objects import glob
|
||||
|
||||
|
||||
class handler(SentryMixin, requestsManager.asyncRequestHandler):
|
||||
@tornado.web.asynchronous
|
||||
@tornado.gen.engine
|
||||
def asyncPost(self):
|
||||
try:
|
||||
# Track time if needed
|
||||
if glob.outputRequestTime:
|
||||
# Start time
|
||||
st = datetime.datetime.now()
|
||||
|
||||
# Client's token string and request data
|
||||
requestTokenString = self.request.headers.get("osu-token")
|
||||
requestData = self.request.body
|
||||
|
||||
# Server's token string and request data
|
||||
responseTokenString = "ayy"
|
||||
responseData = bytes()
|
||||
|
||||
if requestTokenString is None:
|
||||
# No token, first request. Handle login.
|
||||
responseTokenString, responseData = loginEvent.handle(self)
|
||||
else:
|
||||
userToken = None # default value
|
||||
try:
|
||||
# This is not the first packet, send response based on client's request
|
||||
# Packet start position, used to read stacked packets
|
||||
pos = 0
|
||||
|
||||
# Make sure the token exists
|
||||
if requestTokenString not in glob.tokens.tokens:
|
||||
raise exceptions.tokenNotFoundException()
|
||||
|
||||
# Token exists, get its object and lock it
|
||||
userToken = glob.tokens.tokens[requestTokenString]
|
||||
userToken.lock.acquire()
|
||||
|
||||
# Keep reading packets until everything has been read
|
||||
while pos < len(requestData):
|
||||
# Get packet from stack starting from new packet
|
||||
leftData = requestData[pos:]
|
||||
|
||||
# Get packet ID, data length and data
|
||||
packetID = packetHelper.readPacketID(leftData)
|
||||
dataLength = packetHelper.readPacketLength(leftData)
|
||||
packetData = requestData[pos:(pos+dataLength+7)]
|
||||
|
||||
# Console output if needed
|
||||
if glob.outputPackets == True and packetID != 4:
|
||||
log.debug("Incoming packet ({})({}):\n\nPacket code: {}\nPacket length: {}\nSingle packet data: {}\n".format(requestTokenString, userToken.username, str(packetID), str(dataLength), str(packetData)))
|
||||
|
||||
# Event handler
|
||||
def handleEvent(ev):
|
||||
def wrapper():
|
||||
ev.handle(userToken, packetData)
|
||||
return wrapper
|
||||
|
||||
eventHandler = {
|
||||
# TODO: Rename packets and events
|
||||
# TODO: Host check for multi
|
||||
packetIDs.client_changeAction: handleEvent(changeActionEvent),
|
||||
packetIDs.client_logout: handleEvent(logoutEvent),
|
||||
packetIDs.client_friendAdd: handleEvent(friendAddEvent),
|
||||
packetIDs.client_friendRemove: handleEvent(friendRemoveEvent),
|
||||
packetIDs.client_userStatsRequest: handleEvent(userStatsRequestEvent),
|
||||
packetIDs.client_requestStatusUpdate: handleEvent(requestStatusUpdateEvent),
|
||||
packetIDs.client_userPanelRequest: handleEvent(userPanelRequestEvent),
|
||||
|
||||
packetIDs.client_channelJoin: handleEvent(channelJoinEvent),
|
||||
packetIDs.client_channelPart: handleEvent(channelPartEvent),
|
||||
packetIDs.client_sendPublicMessage: handleEvent(sendPublicMessageEvent),
|
||||
packetIDs.client_sendPrivateMessage: handleEvent(sendPrivateMessageEvent),
|
||||
packetIDs.client_setAwayMessage: handleEvent(setAwayMessageEvent),
|
||||
|
||||
packetIDs.client_startSpectating: handleEvent(startSpectatingEvent),
|
||||
packetIDs.client_stopSpectating: handleEvent(stopSpectatingEvent),
|
||||
packetIDs.client_cantSpectate: handleEvent(cantSpectateEvent),
|
||||
packetIDs.client_spectateFrames: handleEvent(spectateFramesEvent),
|
||||
|
||||
packetIDs.client_joinLobby: handleEvent(joinLobbyEvent),
|
||||
packetIDs.client_partLobby: handleEvent(partLobbyEvent),
|
||||
packetIDs.client_createMatch: handleEvent(createMatchEvent),
|
||||
packetIDs.client_joinMatch: handleEvent(joinMatchEvent),
|
||||
packetIDs.client_partMatch: handleEvent(partMatchEvent),
|
||||
packetIDs.client_matchChangeSlot: handleEvent(changeSlotEvent),
|
||||
packetIDs.client_matchChangeSettings: handleEvent(changeMatchSettingsEvent),
|
||||
packetIDs.client_matchChangePassword: handleEvent(changeMatchPasswordEvent),
|
||||
packetIDs.client_matchChangeMods: handleEvent(changeMatchModsEvent),
|
||||
packetIDs.client_matchReady: handleEvent(matchReadyEvent),
|
||||
packetIDs.client_matchNotReady: handleEvent(matchReadyEvent),
|
||||
packetIDs.client_matchLock: handleEvent(matchLockEvent),
|
||||
packetIDs.client_matchStart: handleEvent(matchStartEvent),
|
||||
packetIDs.client_matchLoadComplete: handleEvent(matchPlayerLoadEvent),
|
||||
packetIDs.client_matchSkipRequest: handleEvent(matchSkipEvent),
|
||||
packetIDs.client_matchScoreUpdate: handleEvent(matchFramesEvent),
|
||||
packetIDs.client_matchComplete: handleEvent(matchCompleteEvent),
|
||||
packetIDs.client_matchNoBeatmap: handleEvent(matchNoBeatmapEvent),
|
||||
packetIDs.client_matchHasBeatmap: handleEvent(matchHasBeatmapEvent),
|
||||
packetIDs.client_matchTransferHost: handleEvent(matchTransferHostEvent),
|
||||
packetIDs.client_matchFailed: handleEvent(matchFailedEvent),
|
||||
packetIDs.client_matchChangeTeam: handleEvent(matchChangeTeamEvent),
|
||||
packetIDs.client_invite: handleEvent(matchInviteEvent),
|
||||
|
||||
packetIDs.client_tournamentMatchInfoRequest: handleEvent(tournamentMatchInfoRequestEvent),
|
||||
packetIDs.client_tournamentJoinMatchChannel: handleEvent(tournamentJoinMatchChannelEvent),
|
||||
packetIDs.client_tournamentLeaveMatchChannel: handleEvent(tournamentLeaveMatchChannelEvent),
|
||||
}
|
||||
|
||||
# Packets processed if in restricted mode.
|
||||
# All other packets will be ignored if the user is in restricted mode
|
||||
packetsRestricted = [
|
||||
packetIDs.client_logout,
|
||||
packetIDs.client_userStatsRequest,
|
||||
packetIDs.client_requestStatusUpdate,
|
||||
packetIDs.client_userPanelRequest,
|
||||
packetIDs.client_changeAction,
|
||||
packetIDs.client_channelJoin,
|
||||
packetIDs.client_channelPart,
|
||||
]
|
||||
|
||||
# Process/ignore packet
|
||||
if packetID != 4:
|
||||
if packetID in eventHandler:
|
||||
if userToken.restricted == False or (userToken.restricted == True and packetID in packetsRestricted):
|
||||
eventHandler[packetID]()
|
||||
else:
|
||||
log.warning("Ignored packet id from {} ({}) (user is restricted)".format(requestTokenString, packetID))
|
||||
else:
|
||||
log.warning("Unknown packet id from {} ({})".format(requestTokenString, packetID))
|
||||
|
||||
# Update pos so we can read the next stacked packet
|
||||
# +7 because we add packet ID bytes, unused byte and data length bytes
|
||||
pos += dataLength+7
|
||||
|
||||
# Token queue built, send it
|
||||
responseTokenString = userToken.token
|
||||
responseData = userToken.queue
|
||||
userToken.resetQueue()
|
||||
except exceptions.tokenNotFoundException:
|
||||
# Token not found. Disconnect that user
|
||||
responseData = serverPackets.loginError()
|
||||
responseData += serverPackets.notification("Whoops! Something went wrong, please login again.")
|
||||
log.warning("Received packet from unknown token ({}).".format(requestTokenString))
|
||||
log.info("{} has been disconnected (invalid token)".format(requestTokenString))
|
||||
finally:
|
||||
# Unlock token
|
||||
if userToken is not None:
|
||||
# Update ping time for timeout
|
||||
userToken.updatePingTime()
|
||||
# Release token lock
|
||||
userToken.lock.release()
|
||||
|
||||
if glob.outputRequestTime:
|
||||
# End time
|
||||
et = datetime.datetime.now()
|
||||
|
||||
# Total time:
|
||||
tt = float((et.microsecond-st.microsecond)/1000)
|
||||
log.debug("Request time: {}ms".format(tt))
|
||||
|
||||
# Send server's response to client
|
||||
# We don't use token object because we might not have a token (failed login)
|
||||
if glob.gzip:
|
||||
# First, write the gzipped response
|
||||
self.write(gzip.compress(responseData, int(glob.conf.config["server"]["gziplevel"])))
|
||||
|
||||
# Then, add gzip headers
|
||||
self.add_header("Vary", "Accept-Encoding")
|
||||
self.add_header("Content-Encoding", "gzip")
|
||||
else:
|
||||
# First, write the response
|
||||
self.write(responseData)
|
||||
|
||||
# Add all the headers AFTER the response has been written
|
||||
self.set_status(200)
|
||||
self.add_header("cho-token", responseTokenString)
|
||||
self.add_header("cho-protocol", "19")
|
||||
self.add_header("Connection", "keep-alive")
|
||||
self.add_header("Keep-Alive", "timeout=5, max=100")
|
||||
self.add_header("Content-Type", "text/html; charset=UTF-8")
|
||||
except:
|
||||
log.error("Unknown error!\n```\n{}\n{}```".format(sys.exc_info(), traceback.format_exc()))
|
||||
if glob.sentry:
|
||||
yield tornado.gen.Task(self.captureException, exc_info=True)
|
||||
#finally:
|
||||
# self.finish()
|
||||
|
||||
@tornado.web.asynchronous
|
||||
@tornado.gen.engine
|
||||
def asyncGet(self):
|
||||
html = "<html><head><title>MA MAURO ESISTE?</title><style type='text/css'>body{width:30%}</style></head><body><pre>"
|
||||
html += " _ __<br>"
|
||||
html += " (_) / /<br>"
|
||||
html += " ______ __ ____ ____ / /____<br>"
|
||||
html += " / ___/ / _ \\/ _ \\/ / _ \\<br>"
|
||||
html += " / / / / /_) / /_) / / ____/<br>"
|
||||
html += "/__/ /__/ .___/ .___/__/ \\_____/<br>"
|
||||
html += " / / / /<br>"
|
||||
html += " /__/ /__/<br>"
|
||||
html += "<b>PYTHON > ALL VERSION</b><br><br>"
|
||||
html += "<marquee style='white-space:pre;'><br>"
|
||||
html += " .. o .<br>"
|
||||
html += " o.o o . o<br>"
|
||||
html += " oo...<br>"
|
||||
html += " __[]__<br>"
|
||||
html += " phwr--> _\\:D/_/o_o_o_|__ <span style=\"font-family: 'Comic Sans MS'; font-size: 8pt;\">u wot m8</span><br>"
|
||||
html += " \\\"\"\"\"\"\"\"\"\"\"\"\"\"\"/<br>"
|
||||
html += " \\ . .. .. . /<br>"
|
||||
html += "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^<br>"
|
||||
html += "</marquee><br><strike>reverse engineering a protocol impossible to reverse engineer since always</strike><br>we are actually reverse engineering bancho successfully. for the third time.<br><br><i>© Ripple team, 2016</i></pre></body></html>"
|
||||
self.write(html)
|
266
handlers/mainHandler.pyx
Normal file
266
handlers/mainHandler.pyx
Normal file
@@ -0,0 +1,266 @@
|
||||
import datetime
|
||||
import gzip
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import tornado.gen
|
||||
import tornado.web
|
||||
from raven.contrib.tornado import SentryMixin
|
||||
|
||||
from common.log import logUtils as log
|
||||
from common.web import requestsManager
|
||||
from constants import exceptions
|
||||
from constants import packetIDs
|
||||
from constants import serverPackets
|
||||
from events import cantSpectateEvent
|
||||
from events import changeActionEvent
|
||||
from events import changeMatchModsEvent
|
||||
from events import changeMatchPasswordEvent
|
||||
from events import changeMatchSettingsEvent
|
||||
from events import changeSlotEvent
|
||||
from events import channelJoinEvent
|
||||
from events import channelPartEvent
|
||||
from events import createMatchEvent
|
||||
from events import friendAddEvent
|
||||
from events import friendRemoveEvent
|
||||
from events import joinLobbyEvent
|
||||
from events import joinMatchEvent
|
||||
from events import loginEvent
|
||||
from events import logoutEvent
|
||||
from events import matchChangeTeamEvent
|
||||
from events import matchCompleteEvent
|
||||
from events import matchFailedEvent
|
||||
from events import matchFramesEvent
|
||||
from events import matchHasBeatmapEvent
|
||||
from events import matchInviteEvent
|
||||
from events import matchLockEvent
|
||||
from events import matchNoBeatmapEvent
|
||||
from events import matchPlayerLoadEvent
|
||||
from events import matchReadyEvent
|
||||
from events import matchSkipEvent
|
||||
from events import matchStartEvent
|
||||
from events import matchTransferHostEvent
|
||||
from events import partLobbyEvent
|
||||
from events import partMatchEvent
|
||||
from events import requestStatusUpdateEvent
|
||||
from events import sendPrivateMessageEvent
|
||||
from events import sendPublicMessageEvent
|
||||
from events import setAwayMessageEvent
|
||||
from events import spectateFramesEvent
|
||||
from events import startSpectatingEvent
|
||||
from events import stopSpectatingEvent
|
||||
from events import userPanelRequestEvent
|
||||
from events import userStatsRequestEvent
|
||||
from events import tournamentMatchInfoRequestEvent
|
||||
from events import tournamentJoinMatchChannelEvent
|
||||
from events import tournamentLeaveMatchChannelEvent
|
||||
from helpers import packetHelper
|
||||
from objects import glob
|
||||
from common.sentry import sentry
|
||||
|
||||
|
||||
class handler(requestsManager.asyncRequestHandler):
|
||||
@tornado.web.asynchronous
|
||||
@tornado.gen.engine
|
||||
@sentry.captureTornado
|
||||
def asyncPost(self):
|
||||
# Track time if needed
|
||||
if glob.outputRequestTime:
|
||||
# Start time
|
||||
st = datetime.datetime.now()
|
||||
|
||||
# Client's token string and request data
|
||||
requestTokenString = self.request.headers.get("osu-token")
|
||||
requestData = self.request.body
|
||||
|
||||
# Server's token string and request data
|
||||
responseTokenString = "ayy"
|
||||
responseData = bytes()
|
||||
|
||||
if requestTokenString is None:
|
||||
# No token, first request. Handle login.
|
||||
responseTokenString, responseData = loginEvent.handle(self)
|
||||
else:
|
||||
userToken = None # default value
|
||||
try:
|
||||
# This is not the first packet, send response based on client's request
|
||||
# Packet start position, used to read stacked packets
|
||||
pos = 0
|
||||
|
||||
# Make sure the token exists
|
||||
if requestTokenString not in glob.tokens.tokens:
|
||||
raise exceptions.tokenNotFoundException()
|
||||
|
||||
# Token exists, get its object and lock it
|
||||
userToken = glob.tokens.tokens[requestTokenString]
|
||||
userToken.lock.acquire()
|
||||
|
||||
# Keep reading packets until everything has been read
|
||||
while pos < len(requestData):
|
||||
# Get packet from stack starting from new packet
|
||||
leftData = requestData[pos:]
|
||||
|
||||
# Get packet ID, data length and data
|
||||
packetID = packetHelper.readPacketID(leftData)
|
||||
dataLength = packetHelper.readPacketLength(leftData)
|
||||
packetData = requestData[pos:(pos+dataLength+7)]
|
||||
|
||||
# Console output if needed
|
||||
if glob.outputPackets == True and packetID != 4:
|
||||
log.debug("Incoming packet ({})({}):\n\nPacket code: {}\nPacket length: {}\nSingle packet data: {}\n".format(requestTokenString, userToken.username, str(packetID), str(dataLength), str(packetData)))
|
||||
|
||||
# Event handler
|
||||
def handleEvent(ev):
|
||||
def wrapper():
|
||||
ev.handle(userToken, packetData)
|
||||
return wrapper
|
||||
|
||||
eventHandler = {
|
||||
packetIDs.client_changeAction: handleEvent(changeActionEvent),
|
||||
packetIDs.client_logout: handleEvent(logoutEvent),
|
||||
packetIDs.client_friendAdd: handleEvent(friendAddEvent),
|
||||
packetIDs.client_friendRemove: handleEvent(friendRemoveEvent),
|
||||
packetIDs.client_userStatsRequest: handleEvent(userStatsRequestEvent),
|
||||
packetIDs.client_requestStatusUpdate: handleEvent(requestStatusUpdateEvent),
|
||||
packetIDs.client_userPanelRequest: handleEvent(userPanelRequestEvent),
|
||||
|
||||
packetIDs.client_channelJoin: handleEvent(channelJoinEvent),
|
||||
packetIDs.client_channelPart: handleEvent(channelPartEvent),
|
||||
packetIDs.client_sendPublicMessage: handleEvent(sendPublicMessageEvent),
|
||||
packetIDs.client_sendPrivateMessage: handleEvent(sendPrivateMessageEvent),
|
||||
packetIDs.client_setAwayMessage: handleEvent(setAwayMessageEvent),
|
||||
|
||||
packetIDs.client_startSpectating: handleEvent(startSpectatingEvent),
|
||||
packetIDs.client_stopSpectating: handleEvent(stopSpectatingEvent),
|
||||
packetIDs.client_cantSpectate: handleEvent(cantSpectateEvent),
|
||||
packetIDs.client_spectateFrames: handleEvent(spectateFramesEvent),
|
||||
|
||||
packetIDs.client_joinLobby: handleEvent(joinLobbyEvent),
|
||||
packetIDs.client_partLobby: handleEvent(partLobbyEvent),
|
||||
packetIDs.client_createMatch: handleEvent(createMatchEvent),
|
||||
packetIDs.client_joinMatch: handleEvent(joinMatchEvent),
|
||||
packetIDs.client_partMatch: handleEvent(partMatchEvent),
|
||||
packetIDs.client_matchChangeSlot: handleEvent(changeSlotEvent),
|
||||
packetIDs.client_matchChangeSettings: handleEvent(changeMatchSettingsEvent),
|
||||
packetIDs.client_matchChangePassword: handleEvent(changeMatchPasswordEvent),
|
||||
packetIDs.client_matchChangeMods: handleEvent(changeMatchModsEvent),
|
||||
packetIDs.client_matchReady: handleEvent(matchReadyEvent),
|
||||
packetIDs.client_matchNotReady: handleEvent(matchReadyEvent),
|
||||
packetIDs.client_matchLock: handleEvent(matchLockEvent),
|
||||
packetIDs.client_matchStart: handleEvent(matchStartEvent),
|
||||
packetIDs.client_matchLoadComplete: handleEvent(matchPlayerLoadEvent),
|
||||
packetIDs.client_matchSkipRequest: handleEvent(matchSkipEvent),
|
||||
packetIDs.client_matchScoreUpdate: handleEvent(matchFramesEvent),
|
||||
packetIDs.client_matchComplete: handleEvent(matchCompleteEvent),
|
||||
packetIDs.client_matchNoBeatmap: handleEvent(matchNoBeatmapEvent),
|
||||
packetIDs.client_matchHasBeatmap: handleEvent(matchHasBeatmapEvent),
|
||||
packetIDs.client_matchTransferHost: handleEvent(matchTransferHostEvent),
|
||||
packetIDs.client_matchFailed: handleEvent(matchFailedEvent),
|
||||
packetIDs.client_matchChangeTeam: handleEvent(matchChangeTeamEvent),
|
||||
packetIDs.client_invite: handleEvent(matchInviteEvent),
|
||||
|
||||
packetIDs.client_tournamentMatchInfoRequest: handleEvent(tournamentMatchInfoRequestEvent),
|
||||
packetIDs.client_tournamentJoinMatchChannel: handleEvent(tournamentJoinMatchChannelEvent),
|
||||
packetIDs.client_tournamentLeaveMatchChannel: handleEvent(tournamentLeaveMatchChannelEvent),
|
||||
}
|
||||
|
||||
# Packets processed if in restricted mode.
|
||||
# All other packets will be ignored if the user is in restricted mode
|
||||
packetsRestricted = [
|
||||
packetIDs.client_logout,
|
||||
packetIDs.client_userStatsRequest,
|
||||
packetIDs.client_requestStatusUpdate,
|
||||
packetIDs.client_userPanelRequest,
|
||||
packetIDs.client_changeAction,
|
||||
packetIDs.client_channelJoin,
|
||||
packetIDs.client_channelPart,
|
||||
]
|
||||
|
||||
# Process/ignore packet
|
||||
if packetID != 4:
|
||||
if packetID in eventHandler:
|
||||
if userToken.restricted == False or (userToken.restricted == True and packetID in packetsRestricted):
|
||||
eventHandler[packetID]()
|
||||
else:
|
||||
log.warning("Ignored packet id from {} ({}) (user is restricted)".format(requestTokenString, packetID))
|
||||
else:
|
||||
log.warning("Unknown packet id from {} ({})".format(requestTokenString, packetID))
|
||||
|
||||
# Update pos so we can read the next stacked packet
|
||||
# +7 because we add packet ID bytes, unused byte and data length bytes
|
||||
pos += dataLength+7
|
||||
|
||||
# Token queue built, send it
|
||||
responseTokenString = userToken.token
|
||||
responseData = userToken.queue
|
||||
userToken.resetQueue()
|
||||
except exceptions.tokenNotFoundException:
|
||||
# Token not found. Disconnect that user
|
||||
responseData = serverPackets.loginError()
|
||||
responseData += serverPackets.notification("Whoops! Something went wrong, please login again.")
|
||||
log.warning("Received packet from unknown token ({}).".format(requestTokenString))
|
||||
log.info("{} has been disconnected (invalid token)".format(requestTokenString))
|
||||
finally:
|
||||
# Unlock token
|
||||
if userToken is not None:
|
||||
# Update ping time for timeout
|
||||
userToken.updatePingTime()
|
||||
# Release token lock
|
||||
userToken.lock.release()
|
||||
# Delete token if kicked
|
||||
if userToken.kicked:
|
||||
glob.tokens.deleteToken(userToken)
|
||||
|
||||
if glob.outputRequestTime:
|
||||
# End time
|
||||
et = datetime.datetime.now()
|
||||
|
||||
# Total time:
|
||||
tt = float((et.microsecond-st.microsecond)/1000)
|
||||
log.debug("Request time: {}ms".format(tt))
|
||||
|
||||
# Send server's response to client
|
||||
# We don't use token object because we might not have a token (failed login)
|
||||
if glob.gzip:
|
||||
# First, write the gzipped response
|
||||
self.write(gzip.compress(responseData, int(glob.conf.config["server"]["gziplevel"])))
|
||||
|
||||
# Then, add gzip headers
|
||||
self.add_header("Vary", "Accept-Encoding")
|
||||
self.add_header("Content-Encoding", "gzip")
|
||||
else:
|
||||
# First, write the response
|
||||
self.write(responseData)
|
||||
|
||||
# Add all the headers AFTER the response has been written
|
||||
self.set_status(200)
|
||||
self.add_header("cho-token", responseTokenString)
|
||||
self.add_header("cho-protocol", "19")
|
||||
self.add_header("Connection", "keep-alive")
|
||||
self.add_header("Keep-Alive", "timeout=5, max=100")
|
||||
self.add_header("Content-Type", "text/html; charset=UTF-8")
|
||||
|
||||
@tornado.web.asynchronous
|
||||
@tornado.gen.engine
|
||||
def asyncGet(self):
|
||||
html = "<html><head><title>MA MAURO ESISTE?</title><style type='text/css'>body{width:30%}</style></head><body><pre>"
|
||||
html += " _ __<br>"
|
||||
html += " (_) / /<br>"
|
||||
html += " ______ __ ____ ____ / /____<br>"
|
||||
html += " / ___/ / _ \\/ _ \\/ / _ \\<br>"
|
||||
html += " / / / / /_) / /_) / / ____/<br>"
|
||||
html += "/__/ /__/ .___/ .___/__/ \\_____/<br>"
|
||||
html += " / / / /<br>"
|
||||
html += " /__/ /__/<br>"
|
||||
html += "<b>PYTHON > ALL VERSION</b><br><br>"
|
||||
html += "<marquee style='white-space:pre;'><br>"
|
||||
html += " .. o .<br>"
|
||||
html += " o.o o . o<br>"
|
||||
html += " oo...<br>"
|
||||
html += " __[]__<br>"
|
||||
html += " phwr--> _\\:D/_/o_o_o_|__ <span style=\"font-family: 'Comic Sans MS'; font-size: 8pt;\">u wot m8</span><br>"
|
||||
html += " \\\"\"\"\"\"\"\"\"\"\"\"\"\"\"/<br>"
|
||||
html += " \\ . .. .. . /<br>"
|
||||
html += "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^<br>"
|
||||
html += "</marquee><br><strike>reverse engineering a protocol impossible to reverse engineer since always</strike><br>we are actually reverse engineering bancho successfully. for the third time.<br><br><i>© Ripple team, 2016</i></pre></body></html>"
|
||||
self.write(html)
|
@@ -12,14 +12,11 @@ def joinChannel(userID = 0, channel = "", token = None, toIRC = True):
|
||||
"""
|
||||
Join a channel
|
||||
|
||||
userID -- user ID of the user that joins the channel. Optional.
|
||||
token can be used instead.
|
||||
token -- user token object of user that joins the channel. Optional.
|
||||
userID can be used instead.
|
||||
channel -- name of channe
|
||||
toIRC -- if True, send this channel join event to IRC. Must be true if joining from bancho.
|
||||
Optional. Defaukt: True
|
||||
return -- returns 0 if joined or other IRC code in case of error. Needed only on IRC-side
|
||||
:param userID: user ID of the user that joins the channel. Optional. token can be used instead.
|
||||
: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
|
||||
:return: 0 if joined or other IRC code in case of error. Needed only on IRC-side
|
||||
"""
|
||||
try:
|
||||
# Get token if not defined
|
||||
@@ -30,44 +27,32 @@ 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
|
||||
|
||||
# Check channel permissions
|
||||
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)
|
||||
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(glob.channels.channels[channel])
|
||||
|
||||
# Send channel joined (IRC)
|
||||
if glob.irc == True and toIRC == True:
|
||||
glob.ircServer.banchoJoinChannel(username, channel)
|
||||
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")
|
||||
@@ -77,17 +62,18 @@ def partChannel(userID = 0, channel = "", token = None, toIRC = True, kick = Fal
|
||||
"""
|
||||
Part a channel
|
||||
|
||||
userID -- user ID of the user that parts the channel. Optional.
|
||||
token can be used instead.
|
||||
token -- user token object of user that parts the channel. Optional.
|
||||
userID can be used instead.
|
||||
channel -- name of channel
|
||||
toIRC -- if True, send this channel join event to IRC. Must be true if joining from bancho.
|
||||
Optional. Defaukt: True
|
||||
kick -- if True, channel tab will be closed on client. Used when leaving lobby. Optional. Default: False
|
||||
return -- returns 0 if joined or other IRC code in case of error. Needed only on IRC-side
|
||||
:param userID: user ID of the user that parts the channel. Optional. token can be used instead.
|
||||
:param token: user token object of user that parts 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. Optional. Default: True
|
||||
:param kick: if True, channel tab will be closed on client. Used when leaving lobby. Optional. Default: False
|
||||
: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)
|
||||
@@ -96,10 +82,6 @@ def partChannel(userID = 0, channel = "", token = None, toIRC = True, kick = Fal
|
||||
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)
|
||||
@@ -119,12 +101,20 @@ 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 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 == True 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
|
||||
@@ -133,16 +123,19 @@ def partChannel(userID = 0, channel = "", token = None, toIRC = True, kick = Fal
|
||||
|
||||
# IRC part
|
||||
if glob.irc == True and toIRC == True:
|
||||
glob.ircServer.banchoPartChannel(username, channel)
|
||||
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
|
||||
@@ -151,18 +144,15 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
|
||||
"""
|
||||
Send a message to osu!bancho and IRC server
|
||||
|
||||
fro -- sender username. Optional.
|
||||
You can use token instead of this if you wish.
|
||||
to -- receiver channel (if starts with #) or username
|
||||
message -- text of the message
|
||||
token -- sender token object.
|
||||
You can use this instead of fro if you are sending messages from bancho.
|
||||
Optional.
|
||||
toIRC -- if True, send the message to IRC. If False, send it to Bancho only.
|
||||
Optional. Default: True
|
||||
:param fro: sender username. Optional. token can be used instead
|
||||
:param to: receiver channel (if starts with #) or username
|
||||
:param message: text of the message
|
||||
:param token: sender token object. Optional. fro can be used instead
|
||||
:param toIRC: if True, send the message to IRC. If False, send it to Bancho only. Default: 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)
|
||||
@@ -171,11 +161,7 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
|
||||
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:
|
||||
@@ -189,12 +175,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 = "FokaBot"
|
||||
|
||||
# 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)
|
||||
@@ -204,7 +194,6 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
|
||||
toClient = "#spectator"
|
||||
elif to.startswith("#multi_"):
|
||||
toClient = "#multiplayer"
|
||||
|
||||
# Truncate message if > 2048 characters
|
||||
message = message[:2048]+"..." if len(message) > 2048 else message
|
||||
|
||||
@@ -212,7 +201,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("#")
|
||||
@@ -220,35 +209,31 @@ 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
|
||||
raise exceptions.channelModeratedException()
|
||||
|
||||
# Make sure we have write permissions
|
||||
if glob.channels.channels[to].publicWrite == False and token.admin == False:
|
||||
raise exceptions.channelNoPermissionsException
|
||||
raise exceptions.channelNoPermissionsException()
|
||||
|
||||
# Add message in buffer
|
||||
token.addMessageInBuffer(to, message)
|
||||
|
||||
# Everything seems fine, build recipients list and send packet
|
||||
recipients = glob.channels.channels[to].getConnectedUsers()[:]
|
||||
for key, value in glob.tokens.tokens.items():
|
||||
# Skip our client and irc clients
|
||||
if key == tokenString or value.irc == True:
|
||||
continue
|
||||
# Send to this client if it's connected to the channel
|
||||
if value.userID in recipients:
|
||||
value.enqueue(packet)
|
||||
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":
|
||||
@@ -256,6 +241,10 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
|
||||
|
||||
# TODO: Make sure the recipient has not disabled PMs for non-friends or he's our friend
|
||||
|
||||
# Away check
|
||||
if recipientToken.awayCheck(token.userID):
|
||||
sendMessage(to, fro, "\x01ACTION is away: {}\x01".format(recipientToken.awayMessage))
|
||||
|
||||
# Check message templates (mods/admins only)
|
||||
if message in messageTemplates.templates and token.admin == True:
|
||||
sendMessage(fro, to, messageTemplates.templates[message])
|
||||
@@ -268,38 +257,38 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
|
||||
glob.ircServer.banchoMessage(fro, to, message)
|
||||
|
||||
# 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)
|
||||
fokaMessage = fokabot.fokabotResponse(token.username, to, message)
|
||||
if fokaMessage:
|
||||
sendMessage("FokaBot", to if isChannel else fro, fokaMessage)
|
||||
|
||||
# File and discord logs (public chat only)
|
||||
if to.startswith("#"):
|
||||
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]))
|
||||
if to.startswith("#") and not (message.startswith("\x01ACTION is playing") and to.startswith("#spect_")):
|
||||
log.chat("{fro} @ {to}: {message}".format(fro=token.username, to=to, message=str(message.encode("utf-8"))))
|
||||
glob.schiavo.sendChatlog("**{fro} @ {to}:** {message}".format(fro=token.username, to=to, message=str(message.encode("utf-8"))[2:-1]))
|
||||
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")
|
||||
@@ -308,6 +297,12 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
|
||||
|
||||
""" IRC-Bancho Connect/Disconnect/Join/Part interfaces"""
|
||||
def fixUsernameForBancho(username):
|
||||
"""
|
||||
Convert username from IRC format (without spaces) to Bancho format (with spaces)
|
||||
|
||||
:param username: username to convert
|
||||
:return: converted username
|
||||
"""
|
||||
# If there are no spaces or underscores in the name
|
||||
# return it
|
||||
if " " not in username and "_" not in username:
|
||||
@@ -322,9 +317,22 @@ def fixUsernameForBancho(username):
|
||||
return username.replace("_", " ")
|
||||
|
||||
def fixUsernameForIRC(username):
|
||||
"""
|
||||
Convert an username from Bancho format to IRC format (underscores instead of spaces)
|
||||
|
||||
:param username: username to convert
|
||||
:return: converted username
|
||||
"""
|
||||
return username.replace(" ", "_")
|
||||
|
||||
def IRCConnect(username):
|
||||
"""
|
||||
Handle IRC login bancho-side.
|
||||
Add token and broadcast login packet.
|
||||
|
||||
:param username: username
|
||||
:return:
|
||||
"""
|
||||
userID = userUtils.getID(username)
|
||||
if not userID:
|
||||
log.warning("{} doesn't exist".format(username))
|
||||
@@ -335,6 +343,13 @@ def IRCConnect(username):
|
||||
log.info("{} logged in from IRC".format(username))
|
||||
|
||||
def IRCDisconnect(username):
|
||||
"""
|
||||
Handle IRC logout bancho-side.
|
||||
Remove token and broadcast logout packet.
|
||||
|
||||
:param username: username
|
||||
:return:
|
||||
"""
|
||||
token = glob.tokens.getTokenFromUsername(username)
|
||||
if token is None:
|
||||
log.warning("{} doesn't exist".format(username))
|
||||
@@ -343,6 +358,13 @@ def IRCDisconnect(username):
|
||||
log.info("{} disconnected from IRC".format(username))
|
||||
|
||||
def IRCJoinChannel(username, channel):
|
||||
"""
|
||||
Handle IRC channel join bancho-side.
|
||||
|
||||
:param username: username
|
||||
:param channel: channel name
|
||||
:return: IRC return code
|
||||
"""
|
||||
userID = userUtils.getID(username)
|
||||
if not userID:
|
||||
log.warning("{} doesn't exist".format(username))
|
||||
@@ -353,8 +375,30 @@ def IRCJoinChannel(username, channel):
|
||||
return joinChannel(userID, channel)
|
||||
|
||||
def IRCPartChannel(username, channel):
|
||||
"""
|
||||
Handle IRC channel part bancho-side.
|
||||
|
||||
:param username: username
|
||||
:param channel: channel name
|
||||
:return: IRC return code
|
||||
"""
|
||||
userID = userUtils.getID(username)
|
||||
if not userID:
|
||||
log.warning("{} doesn't exist".format(username))
|
||||
return
|
||||
return partChannel(userID, channel)
|
||||
|
||||
def IRCAway(username, message):
|
||||
"""
|
||||
Handle IRC away command bancho-side.
|
||||
|
||||
:param username:
|
||||
:param message: away message
|
||||
:return: IRC return code
|
||||
"""
|
||||
userID = userUtils.getID(username)
|
||||
if not userID:
|
||||
log.warning("{} doesn't exist".format(username))
|
||||
return
|
||||
glob.tokens.getTokenFromUserID(userID).awayMessage = message
|
||||
return 305 if message == "" else 306
|
@@ -5,9 +5,9 @@ class config:
|
||||
# Check if config.ini exists and load/generate it
|
||||
def __init__(self, file):
|
||||
"""
|
||||
Initialize a config object
|
||||
Initialize a config file object
|
||||
|
||||
file -- filename
|
||||
:param file: file name
|
||||
"""
|
||||
self.config = configparser.ConfigParser()
|
||||
self.default = True
|
||||
@@ -25,9 +25,9 @@ class config:
|
||||
# Check if config.ini has all needed the keys
|
||||
def checkConfig(self):
|
||||
"""
|
||||
Check if this config has the required keys
|
||||
Check is the config file has all required keys
|
||||
|
||||
return -- True if valid, False if not
|
||||
:return: True if valid, False if not valid
|
||||
"""
|
||||
try:
|
||||
# Try to get all the required keys
|
||||
@@ -37,6 +37,11 @@ class config:
|
||||
self.config.get("db","database")
|
||||
self.config.get("db","workers")
|
||||
|
||||
self.config.get("redis","host")
|
||||
self.config.get("redis","port")
|
||||
self.config.get("redis","database")
|
||||
self.config.get("redis","password")
|
||||
|
||||
self.config.get("server","port")
|
||||
self.config.get("server","threads")
|
||||
self.config.get("server","gzip")
|
||||
@@ -69,12 +74,14 @@ class config:
|
||||
self.config.get("localize","enable")
|
||||
self.config.get("localize","ipapiurl")
|
||||
return True
|
||||
except:
|
||||
except configparser.Error:
|
||||
return False
|
||||
|
||||
def generateDefaultConfig(self):
|
||||
"""
|
||||
Open and set default keys for that config file
|
||||
Write a default config file to disk
|
||||
|
||||
:return:
|
||||
"""
|
||||
# Open config.ini in write mode
|
||||
f = open(self.fileName, "w")
|
||||
@@ -87,6 +94,12 @@ class config:
|
||||
self.config.set("db", "database", "ripple")
|
||||
self.config.set("db", "workers", "4")
|
||||
|
||||
self.config.add_section("redis")
|
||||
self.config.set("redis", "host", "localhost")
|
||||
self.config.set("redis", "port", "6379")
|
||||
self.config.set("redis", "database", "0")
|
||||
self.config.set("redis", "password", "")
|
||||
|
||||
self.config.add_section("server")
|
||||
self.config.set("server", "port", "5001")
|
||||
self.config.set("server", "threads", "16")
|
||||
@@ -114,9 +127,9 @@ class config:
|
||||
self.config.set("discord", "devgroup", "")
|
||||
|
||||
self.config.add_section("datadog")
|
||||
self.config.set("datadog", "enable")
|
||||
self.config.set("datadog", "apikey")
|
||||
self.config.set("datadog", "appkey")
|
||||
self.config.set("datadog", "enable", "0")
|
||||
self.config.set("datadog", "apikey", "")
|
||||
self.config.set("datadog", "appkey", "")
|
||||
|
||||
self.config.add_section("irc")
|
||||
self.config.set("irc", "enable", "1")
|
||||
|
@@ -1,11 +1,12 @@
|
||||
from common.constants import bcolors
|
||||
from objects import glob
|
||||
|
||||
def printServerStartHeader(asciiArt):
|
||||
def printServerStartHeader(asciiArt=True):
|
||||
"""
|
||||
Print server start header with optional ascii art
|
||||
Print server start message
|
||||
|
||||
asciiArt -- if True, will print ascii art too
|
||||
:param asciiArt: print BanchoBoat ascii art. Default: True
|
||||
:return:
|
||||
"""
|
||||
if asciiArt:
|
||||
print("{} _ __".format(bcolors.GREEN))
|
||||
@@ -32,35 +33,43 @@ def printServerStartHeader(asciiArt):
|
||||
|
||||
def printNoNl(string):
|
||||
"""
|
||||
Print string without new line at the end
|
||||
Print a string without \n at the end
|
||||
|
||||
string -- string to print
|
||||
:param string: string to print
|
||||
:return:
|
||||
"""
|
||||
print(string, end="")
|
||||
|
||||
def printColored(string, color):
|
||||
"""
|
||||
Print colored string
|
||||
Print a colored string
|
||||
|
||||
string -- string to print
|
||||
color -- see bcolors.py
|
||||
:param string: string to print
|
||||
:param color: ANSI color code
|
||||
:return:
|
||||
"""
|
||||
print("{}{}{}".format(color, string, bcolors.ENDC))
|
||||
|
||||
def printError():
|
||||
"""
|
||||
Print error text FOR LOADING
|
||||
Print a red "Error"
|
||||
|
||||
:return:
|
||||
"""
|
||||
printColored("Error", bcolors.RED)
|
||||
|
||||
def printDone():
|
||||
"""
|
||||
Print error text FOR LOADING
|
||||
Print a green "Done"
|
||||
|
||||
:return:
|
||||
"""
|
||||
printColored("Done", bcolors.GREEN)
|
||||
|
||||
def printWarning():
|
||||
"""
|
||||
Print error text FOR LOADING
|
||||
Print a yellow "Warning"
|
||||
|
||||
:return:
|
||||
"""
|
||||
printColored("Warning", bcolors.YELLOW)
|
||||
|
@@ -1,5 +1,4 @@
|
||||
"""Contains all country codes with their osu numeric code"""
|
||||
|
||||
# TODO: Update countries list
|
||||
countryCodes = {
|
||||
"LV": 132,
|
||||
"AD": 3,
|
||||
@@ -255,12 +254,11 @@ countryCodes = {
|
||||
|
||||
def getCountryID(code):
|
||||
"""
|
||||
Get country ID for osu client
|
||||
Get osu country ID from country letters
|
||||
|
||||
code -- country name abbreviation (eg: US)
|
||||
return -- country code int
|
||||
:param code: country letters (eg: US)
|
||||
:return: country osu code
|
||||
"""
|
||||
|
||||
if code in countryCodes:
|
||||
return countryCodes[code]
|
||||
else:
|
||||
@@ -270,10 +268,9 @@ def getCountryLetters(code):
|
||||
"""
|
||||
Get country letters from osu country ID
|
||||
|
||||
code -- country code int
|
||||
return -- country name (2 letters) (XX if code not found)
|
||||
:param code: osu country ID
|
||||
:return: country letters (XX if not found)
|
||||
"""
|
||||
|
||||
for key, value in countryCodes.items():
|
||||
if value == code:
|
||||
return key
|
||||
|
@@ -7,10 +7,10 @@ from objects import glob
|
||||
|
||||
def getCountry(ip):
|
||||
"""
|
||||
Get country from IP address
|
||||
Get country from IP address using geoip api
|
||||
|
||||
ip -- IP Address
|
||||
return -- Country code (2 letters)
|
||||
:param ip: IP address
|
||||
:return: country code. XX if invalid.
|
||||
"""
|
||||
try:
|
||||
# Try to get country from Pikolo Aul's Go-Sanic ip API
|
||||
@@ -22,15 +22,15 @@ def getCountry(ip):
|
||||
|
||||
def getLocation(ip):
|
||||
"""
|
||||
Get latitude and longitude from IP address
|
||||
Get latitude and longitude from IP address using geoip api
|
||||
|
||||
ip -- IP address
|
||||
return -- [latitude, longitude]
|
||||
:param ip: IP address
|
||||
:return: (latitude, longitude)
|
||||
"""
|
||||
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
|
||||
|
@@ -1,15 +1,15 @@
|
||||
import struct
|
||||
from constants import dataTypes
|
||||
|
||||
def uleb128Encode(num):
|
||||
cpdef bytearray uleb128Encode(int num):
|
||||
"""
|
||||
Encode int -> uleb128
|
||||
Encode an int to uleb128
|
||||
|
||||
num -- int to encode
|
||||
return -- bytearray with encoded number
|
||||
:param num: int to encode
|
||||
:return: bytearray with encoded number
|
||||
"""
|
||||
arr = bytearray()
|
||||
length = 0
|
||||
cdef bytearray arr = bytearray()
|
||||
cdef int length = 0
|
||||
|
||||
if num == 0:
|
||||
return bytearray(b"\x00")
|
||||
@@ -23,15 +23,16 @@ def uleb128Encode(num):
|
||||
|
||||
return arr
|
||||
|
||||
def uleb128Decode(num):
|
||||
cpdef list uleb128Decode(bytes num):
|
||||
"""
|
||||
Decode uleb128 -> int
|
||||
Decode a uleb128 to int
|
||||
|
||||
num -- encoded uleb128
|
||||
return -- list. [total, length]
|
||||
:param num: encoded uleb128 int
|
||||
:return: (total, length)
|
||||
"""
|
||||
shift = 0
|
||||
arr = [0,0] #total, length
|
||||
cdef int shift = 0
|
||||
cdef list arr = [0,0] #total, length
|
||||
cdef int b
|
||||
|
||||
while True:
|
||||
b = num[arr[1]]
|
||||
@@ -43,16 +44,14 @@ def uleb128Decode(num):
|
||||
|
||||
return arr
|
||||
|
||||
def unpackData(data, dataType):
|
||||
cpdef unpackData(bytes data, int dataType):
|
||||
"""
|
||||
Unpacks data according to dataType
|
||||
Unpacks a single section of a packet.
|
||||
|
||||
data -- bytes array to unpack
|
||||
dataType -- data type. See dataTypes.py
|
||||
|
||||
return -- unpacked bytes
|
||||
:param data: bytes to unpack
|
||||
:param dataType: data type
|
||||
:return: unpacked bytes
|
||||
"""
|
||||
|
||||
# Get right pack Type
|
||||
if dataType == dataTypes.UINT16:
|
||||
unpackType = "<H"
|
||||
@@ -76,18 +75,17 @@ def unpackData(data, dataType):
|
||||
# Unpack
|
||||
return struct.unpack(unpackType, bytes(data))[0]
|
||||
|
||||
def packData(__data, dataType):
|
||||
cpdef bytes packData(__data, int dataType):
|
||||
"""
|
||||
Packs data according to dataType
|
||||
Packs a single section of a packet.
|
||||
|
||||
data -- bytes to pack
|
||||
dataType -- data type. See dataTypes.py
|
||||
|
||||
return -- packed bytes
|
||||
:param __data: data to pack
|
||||
:param dataType: data type
|
||||
:return: packed bytes
|
||||
"""
|
||||
|
||||
data = bytes() # data to return
|
||||
pack = True # if True, use pack. False only with strings
|
||||
cdef bytes data = bytes() # data to return
|
||||
cdef bint pack = True # if True, use pack. False only with strings
|
||||
cdef str packType
|
||||
|
||||
# Get right pack Type
|
||||
if dataType == dataTypes.BBYTES:
|
||||
@@ -138,23 +136,24 @@ def packData(__data, dataType):
|
||||
|
||||
return data
|
||||
|
||||
def buildPacket(__packet, __packetData=None):
|
||||
cpdef bytes buildPacket(int __packet, list __packetData = None):
|
||||
"""
|
||||
Build a packet
|
||||
Builds a packet
|
||||
|
||||
packet -- packet id (int)
|
||||
packetData -- list [[data, dataType], [data, dataType], ...]
|
||||
|
||||
return -- packet bytes
|
||||
:param __packet: packet ID
|
||||
:param __packetData: packet structure [[data, dataType], [data, dataType], ...]
|
||||
:return: packet bytes
|
||||
"""
|
||||
# Set some variables
|
||||
# Default argument
|
||||
if __packetData is None:
|
||||
__packetData = []
|
||||
packetData = bytes()
|
||||
packetLength = 0
|
||||
packetBytes = bytes()
|
||||
# Set some variables
|
||||
cdef bytes packetData = bytes()
|
||||
cdef int packetLength = 0
|
||||
cdef bytes packetBytes = bytes()
|
||||
|
||||
# Pack packet data
|
||||
cdef list i
|
||||
for i in __packetData:
|
||||
packetData += packData(i[0], i[1])
|
||||
|
||||
@@ -168,42 +167,43 @@ def buildPacket(__packet, __packetData=None):
|
||||
packetBytes += packetData # packet data
|
||||
return packetBytes
|
||||
|
||||
def readPacketID(stream):
|
||||
cpdef int readPacketID(bytes stream):
|
||||
"""
|
||||
Read packetID from stream (0-1 bytes)
|
||||
Read packetID (first two bytes) from a packet
|
||||
|
||||
stream -- data stream
|
||||
return -- packet ID (int)
|
||||
:param stream: packet bytes
|
||||
:return: packet ID
|
||||
"""
|
||||
return unpackData(stream[0:2], dataTypes.UINT16)
|
||||
|
||||
def readPacketLength(stream):
|
||||
cpdef int readPacketLength(bytes stream):
|
||||
"""
|
||||
Read packet length from stream (3-4-5-6 bytes)
|
||||
Read packet data length (3:7 bytes) from a packet
|
||||
|
||||
stream -- data stream
|
||||
return -- packet length (int)
|
||||
:param stream: packet bytes
|
||||
:return: packet data length
|
||||
"""
|
||||
return unpackData(stream[3:7], dataTypes.UINT32)
|
||||
|
||||
|
||||
def readPacketData(stream, structure=None, hasFirstBytes = True):
|
||||
cpdef readPacketData(bytes stream, list structure=None, bint hasFirstBytes = True):
|
||||
"""
|
||||
Read packet data from stream according to structure
|
||||
|
||||
stream -- data stream
|
||||
structure -- [[name, dataType], [name, dataType], ...]
|
||||
hasFirstBytes -- if True, stream has packetID and length bytes.
|
||||
if False, stream has only packetData.
|
||||
Optional. Default: True
|
||||
return -- dictionary. key: name, value: read data
|
||||
Read packet data from `stream` according to `structure`
|
||||
:param stream: packet bytes
|
||||
:param structure: packet structure: [[name, dataType], [name, dataType], ...]
|
||||
:param hasFirstBytes: if True, `stream` has packetID and length bytes.
|
||||
if False, `stream` has only packet data. Default: True
|
||||
:return: {name: unpackedValue, ...}
|
||||
"""
|
||||
# Read packet ID (first 2 bytes)
|
||||
# Default list argument
|
||||
if structure is None:
|
||||
structure = []
|
||||
data = {}
|
||||
|
||||
# Read packet ID (first 2 bytes)
|
||||
cdef dict data = {}
|
||||
|
||||
# Skip packet ID and packet length if needed
|
||||
cdef start, end
|
||||
if hasFirstBytes:
|
||||
end = 7
|
||||
start = 7
|
||||
@@ -212,6 +212,8 @@ def readPacketData(stream, structure=None, hasFirstBytes = True):
|
||||
start = 0
|
||||
|
||||
# Read packet
|
||||
cdef list i
|
||||
cdef bint unpack
|
||||
for i in structure:
|
||||
start = end
|
||||
unpack = True
|
||||
@@ -246,7 +248,10 @@ def readPacketData(stream, structure=None, hasFirstBytes = True):
|
||||
end = start+length[0]+length[1]+1
|
||||
|
||||
# Read bytes
|
||||
data[i[0]] = ''.join(chr(j) for j in stream[start+1+length[1]:end])
|
||||
#data[i[0]] = ''.join(chr(j) for j in stream[start+1+length[1]:end])
|
||||
data[i[0]] = ""
|
||||
for j in stream[start+1+length[1]:end]:
|
||||
data[i[0]] += chr(j)
|
||||
elif i[1] == dataTypes.BYTE:
|
||||
end = start+1
|
||||
elif i[1] == dataTypes.UINT16 or i[1] == dataTypes.SINT16:
|
@@ -17,6 +17,7 @@ from objects import glob
|
||||
def dispose():
|
||||
"""
|
||||
Perform some clean up. Called on shutdown.
|
||||
|
||||
:return:
|
||||
"""
|
||||
print("> Disposing server... ")
|
||||
@@ -27,7 +28,7 @@ def runningUnderUnix():
|
||||
"""
|
||||
Get if the server is running under UNIX or NT
|
||||
|
||||
return --- True if running under UNIX, otherwise False
|
||||
:return: True if running under UNIX, otherwise False
|
||||
"""
|
||||
return True if os.name == "posix" else False
|
||||
|
||||
@@ -35,12 +36,14 @@ def scheduleShutdown(sendRestartTime, restart, message = "", delay=20):
|
||||
"""
|
||||
Schedule a server shutdown/restart
|
||||
|
||||
sendRestartTime -- time (seconds) to wait before sending server restart packets to every client
|
||||
restart -- if True, server will restart. if False, server will shudown
|
||||
message -- if set, send that message to every client to warn about the shutdown/restart
|
||||
:param sendRestartTime: time (seconds) to wait before sending server restart packets to every client
|
||||
:param restart: if True, server will restart. if False, server will shudown
|
||||
:param message: if set, send that message to every client to warn about the shutdown/restart
|
||||
:param delay: additional restart delay in seconds. Default: 20
|
||||
:return:
|
||||
"""
|
||||
# Console output
|
||||
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
|
||||
@@ -61,13 +64,21 @@ def scheduleShutdown(sendRestartTime, restart, message = "", delay=20):
|
||||
threading.Timer(sendRestartTime+delay, action).start()
|
||||
|
||||
def restartServer():
|
||||
"""Restart pep.py script"""
|
||||
"""
|
||||
Restart pep.py
|
||||
|
||||
:return:
|
||||
"""
|
||||
log.info("Restarting pep.py...")
|
||||
dispose()
|
||||
os.execv(sys.executable, [sys.executable] + sys.argv)
|
||||
|
||||
def shutdownServer():
|
||||
"""Shutdown pep.py"""
|
||||
"""
|
||||
Shutdown pep.py
|
||||
|
||||
:return:
|
||||
"""
|
||||
log.info("Shutting down pep.py...")
|
||||
dispose()
|
||||
sig = signal.SIGKILL if runningUnderUnix() else signal.CTRL_C_EVENT
|
||||
@@ -77,7 +88,7 @@ def getSystemInfo():
|
||||
"""
|
||||
Get a dictionary with some system/server info
|
||||
|
||||
return -- ["unix", "connectedUsers", "webServer", "cpuUsage", "totalMemory", "usedMemory", "loadAverage"]
|
||||
:return: ["unix", "connectedUsers", "webServer", "cpuUsage", "totalMemory", "usedMemory", "loadAverage"]
|
||||
"""
|
||||
data = {"unix": runningUnderUnix(), "connectedUsers": len(glob.tokens.tokens), "matches": len(glob.matches.matches)}
|
||||
|
||||
|
162
irc/ircserver.py
162
irc/ircserver.py
@@ -22,18 +22,15 @@ from objects import glob
|
||||
|
||||
|
||||
class Client:
|
||||
"""
|
||||
IRC Client object
|
||||
"""
|
||||
__linesep_regexp = re.compile(r"\r?\n")
|
||||
|
||||
|
||||
def __init__(self, server, sock):
|
||||
"""
|
||||
Initialize a Client object
|
||||
|
||||
server -- server object
|
||||
sock -- socket connection object
|
||||
:param server: server object
|
||||
:param sock: socket connection object
|
||||
:return:
|
||||
"""
|
||||
self.__timestamp = time.time()
|
||||
self.__readbuffer = ""
|
||||
@@ -60,7 +57,8 @@ class Client:
|
||||
Add a message (basic string) to client buffer.
|
||||
This is the lowest possible level.
|
||||
|
||||
msg -- message to add
|
||||
:param msg: message to add
|
||||
:return:
|
||||
"""
|
||||
self.__writebuffer += msg + "\r\n"
|
||||
|
||||
@@ -69,7 +67,7 @@ class Client:
|
||||
"""
|
||||
Return this client's write buffer size
|
||||
|
||||
return -- write buffer size
|
||||
:return: write buffer size
|
||||
"""
|
||||
return len(self.__writebuffer)
|
||||
|
||||
@@ -78,7 +76,8 @@ class Client:
|
||||
"""
|
||||
Add an IRC-like message to client buffer.
|
||||
|
||||
msg -- message (without IRC stuff)
|
||||
:param msg: message (without IRC stuff)
|
||||
:return:
|
||||
"""
|
||||
self.message(":{} {}".format(self.server.host, msg))
|
||||
|
||||
@@ -87,10 +86,11 @@ class Client:
|
||||
"""
|
||||
Add an IRC-like message to client buffer with code
|
||||
|
||||
code -- response code
|
||||
message -- response message
|
||||
nickname -- receiver nickname
|
||||
channel -- optional
|
||||
:param code: response code
|
||||
:param message: response message
|
||||
:param nickname: receiver nickname
|
||||
:param channel: optional
|
||||
:return:
|
||||
"""
|
||||
if nickname == "":
|
||||
nickname = self.IRCUsername
|
||||
@@ -103,7 +103,8 @@ class Client:
|
||||
"""
|
||||
Add a 403 reply (no such channel) to client buffer.
|
||||
|
||||
channel -- meh
|
||||
:param channel:
|
||||
:return:
|
||||
"""
|
||||
self.replyCode(403, "{} :No such channel".format(channel))
|
||||
|
||||
@@ -112,7 +113,8 @@ class Client:
|
||||
"""
|
||||
Add a 461 reply (not enough parameters) to client buffer
|
||||
|
||||
command -- command that had not enough parameters
|
||||
:param command: name of the command that had not enough parameters
|
||||
:return:
|
||||
"""
|
||||
self.replyCode(403, "{} :Not enough parameters".format(command))
|
||||
|
||||
@@ -121,8 +123,9 @@ class Client:
|
||||
"""
|
||||
Disconnects this client from the IRC server
|
||||
|
||||
quitmsg -- IRC quit message. Default: 'Client quit'
|
||||
callLogout -- if True, call logoutEvent on bancho
|
||||
:param quitmsg: IRC quit message. Default: 'Client quit'
|
||||
:param callLogout: if True, call logoutEvent on bancho
|
||||
:return:
|
||||
"""
|
||||
# Send error to client and close socket
|
||||
self.message("ERROR :{}".format(quitmsg))
|
||||
@@ -133,12 +136,16 @@ class Client:
|
||||
self.server.removeClient(self, quitmsg)
|
||||
|
||||
# Bancho logout
|
||||
if callLogout:
|
||||
if callLogout and self.banchoUsername != "":
|
||||
chat.IRCDisconnect(self.IRCUsername)
|
||||
|
||||
|
||||
def readSocket(self):
|
||||
"""Read data coming from this client socket"""
|
||||
"""
|
||||
Read data coming from this client socket
|
||||
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
# Try to read incoming data from socket
|
||||
data = self.socket.recv(2 ** 10)
|
||||
@@ -161,7 +168,11 @@ class Client:
|
||||
|
||||
|
||||
def parseBuffer(self):
|
||||
"""Parse self.__readbuffer, get command, arguments and call its handler"""
|
||||
"""
|
||||
Parse self.__readbuffer, get command, arguments and call its handler
|
||||
|
||||
:return:
|
||||
"""
|
||||
# Get lines from buffer
|
||||
lines = self.__linesep_regexp.split(self.__readbuffer)
|
||||
self.__readbuffer = lines[-1]
|
||||
@@ -198,7 +209,11 @@ class Client:
|
||||
|
||||
|
||||
def writeSocket(self):
|
||||
"""Write buffer to socket"""
|
||||
"""
|
||||
Write buffer to socket
|
||||
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
sent = self.socket.send(self.__writebuffer.encode())
|
||||
log.debug("[IRC] [{}:{}] <- {}".format(self.ip, self.port, self.__writebuffer[:sent]))
|
||||
@@ -206,9 +221,13 @@ class Client:
|
||||
except socket.error as x:
|
||||
self.disconnect(str(x))
|
||||
|
||||
|
||||
def checkAlive(self):
|
||||
"""Check if this client is still connected"""
|
||||
"""
|
||||
Check if this client is still connected.
|
||||
If the client is dead, disconnect it.
|
||||
|
||||
:return:
|
||||
"""
|
||||
now = time.time()
|
||||
if self.__timestamp + 180 < now:
|
||||
self.disconnect("ping timeout")
|
||||
@@ -224,11 +243,19 @@ class Client:
|
||||
|
||||
|
||||
def sendLusers(self):
|
||||
"""Send lusers response to this client"""
|
||||
"""
|
||||
Send lusers response to this client
|
||||
|
||||
:return:
|
||||
"""
|
||||
self.replyCode(251, "There are {} users and 0 services on 1 server".format(len(glob.tokens.tokens)))
|
||||
|
||||
def sendMotd(self):
|
||||
"""Send MOTD to this client"""
|
||||
"""
|
||||
Send MOTD to this client
|
||||
|
||||
:return:
|
||||
"""
|
||||
self.replyCode(375, "- {} Message of the day - ".format(self.server.host))
|
||||
if len(self.server.motd) == 0:
|
||||
self.replyCode(422, "MOTD File is missing")
|
||||
@@ -296,7 +323,6 @@ class Client:
|
||||
# Disconnect other IRC clients from the same user
|
||||
for _, value in self.server.clients.items():
|
||||
if value.IRCUsername.lower() == self.IRCUsername.lower() and value != self:
|
||||
print("DISCONNECTERINOOOOOOOOOOOOOOOOOOOOO")
|
||||
value.disconnect(quitmsg="Connected from another client")
|
||||
return
|
||||
elif command == "USER":
|
||||
@@ -324,11 +350,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")
|
||||
@@ -341,13 +367,13 @@ class Client:
|
||||
|
||||
# TODO: Part all channels
|
||||
if arguments[0] == "0":
|
||||
return
|
||||
'''for (channelname, channel) in self.channels.items():
|
||||
self.message_channel(channel, "PART", channelname, True)
|
||||
self.channel_log(channel, "left", meta=True)
|
||||
server.remove_member_from_channel(self, channelname)
|
||||
self.channels = {}
|
||||
return'''
|
||||
return
|
||||
|
||||
# Get channels to join list
|
||||
channels = arguments[0].split(",")
|
||||
@@ -376,13 +402,15 @@ class Client:
|
||||
self.replyCode(332, description, channel=channel)
|
||||
|
||||
# Build connected users list
|
||||
users = glob.channels.channels[channel].getConnectedUsers()[:]
|
||||
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
|
||||
@@ -393,7 +421,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")
|
||||
@@ -440,7 +468,6 @@ class Client:
|
||||
|
||||
# Send the message to bancho and reply
|
||||
if not recipientIRC.startswith("#"):
|
||||
print("PMPMPM!!!!!!!!!!")
|
||||
recipientBancho = chat.fixUsernameForBancho(recipientIRC)
|
||||
else:
|
||||
recipientBancho = recipientIRC
|
||||
@@ -478,7 +505,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")
|
||||
@@ -489,10 +516,17 @@ class Client:
|
||||
"""(fake) PONG command handler"""
|
||||
pass
|
||||
|
||||
def awayHandler(self, _, arguments):
|
||||
"""AWAY command handler"""
|
||||
response = chat.IRCAway(self.banchoUsername, " ".join(arguments))
|
||||
self.replyCode(response, "You are no longer marked as being away" if response == 305 else "You have been marked as being away")
|
||||
|
||||
def mainHandler(self, command, arguments):
|
||||
"""Handler for post-login commands"""
|
||||
"""
|
||||
Handler for post-login commands
|
||||
"""
|
||||
handlers = {
|
||||
#"AWAY": away_handler,
|
||||
"AWAY": self.awayHandler,
|
||||
#"ISON": ison_handler,
|
||||
"JOIN": self.joinHandler,
|
||||
#"LIST": list_handler,
|
||||
@@ -518,14 +552,8 @@ class Client:
|
||||
self.replyCode(421, "Unknown command ({})".format(command))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Server:
|
||||
def __init__(self, port):
|
||||
#self.host = socket.getfqdn("127.0.0.1")[:63]
|
||||
self.host = glob.conf.config["irc"]["hostname"]
|
||||
self.port = port
|
||||
self.clients = {} # Socket - - > Client instance.
|
||||
@@ -535,7 +563,9 @@ class Server:
|
||||
"""
|
||||
Disconnect someone from IRC if connected
|
||||
|
||||
username -- victim
|
||||
:param username: victim
|
||||
:param isBanchoUsername: if True, username is a bancho username, else convert it to a bancho username
|
||||
:return:
|
||||
"""
|
||||
for _, value in self.clients.items():
|
||||
if (value.IRCUsername == username and not isBanchoUsername) or (value.banchoUsername == username and isBanchoUsername):
|
||||
@@ -546,8 +576,9 @@ class Server:
|
||||
"""
|
||||
Let every IRC client connected to a specific client know that 'username' joined the channel from bancho
|
||||
|
||||
username -- username of bancho user
|
||||
channel -- joined channel name
|
||||
:param username: username of bancho user
|
||||
:param channel: joined channel name
|
||||
:return:
|
||||
"""
|
||||
username = chat.fixUsernameForIRC(username)
|
||||
for _, value in self.clients.items():
|
||||
@@ -558,8 +589,9 @@ class Server:
|
||||
"""
|
||||
Let every IRC client connected to a specific client know that 'username' parted the channel from bancho
|
||||
|
||||
username -- username of bancho user
|
||||
channel -- joined channel name
|
||||
:param username: username of bancho user
|
||||
:param channel: joined channel name
|
||||
:return:
|
||||
"""
|
||||
username = chat.fixUsernameForIRC(username)
|
||||
for _, value in self.clients.items():
|
||||
@@ -570,9 +602,10 @@ class Server:
|
||||
"""
|
||||
Send a message to IRC when someone sends it from bancho
|
||||
|
||||
fro -- sender username
|
||||
to -- receiver username
|
||||
message -- text of the message
|
||||
:param fro: sender username
|
||||
:param to: receiver username
|
||||
:param message: text of the message
|
||||
:return:
|
||||
"""
|
||||
fro = chat.fixUsernameForIRC(fro)
|
||||
to = chat.fixUsernameForIRC(to)
|
||||
@@ -588,19 +621,24 @@ class Server:
|
||||
value.message(":{} PRIVMSG {} :{}".format(fro, to, message))
|
||||
|
||||
|
||||
def removeClient(self, client, quitmsg):
|
||||
def removeClient(self, client, _):
|
||||
"""
|
||||
Remove a client from connected clients
|
||||
|
||||
client -- client object
|
||||
quitmsg -- QUIT argument, useless atm
|
||||
:param client: client object
|
||||
:return:
|
||||
"""
|
||||
if client.socket in self.clients:
|
||||
del self.clients[client.socket]
|
||||
|
||||
def start(self):
|
||||
"""Start IRC server main loop"""
|
||||
"""
|
||||
Start IRC server main loop
|
||||
|
||||
:return:
|
||||
"""
|
||||
# Sentry
|
||||
sentryClient = None
|
||||
if glob.sentry:
|
||||
sentryClient = raven.Client(glob.conf.config["sentry"]["ircdns"])
|
||||
|
||||
@@ -622,7 +660,7 @@ class Server:
|
||||
[x.socket for x in self.clients.values()
|
||||
if x.writeBufferSize() > 0],
|
||||
[],
|
||||
2)
|
||||
1)
|
||||
|
||||
# Handle incoming connections
|
||||
for x in iwtd:
|
||||
@@ -633,7 +671,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:
|
||||
@@ -652,9 +690,15 @@ 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):
|
||||
"""
|
||||
Create and start an IRC server
|
||||
|
||||
:param port: IRC port. Default: 6667
|
||||
:return:
|
||||
"""
|
||||
glob.ircServer = Server(port)
|
||||
glob.ircServer.start()
|
||||
|
@@ -1,5 +1,6 @@
|
||||
# TODO: Rewrite this shit
|
||||
from common import generalUtils
|
||||
from constants import serverPackets
|
||||
from objects import glob
|
||||
|
||||
|
||||
@@ -41,3 +42,20 @@ class banchoConfig:
|
||||
"""
|
||||
self.config["banchoMaintenance"] = maintenance
|
||||
glob.db.execute("UPDATE bancho_settings SET value_int = %s WHERE name = 'bancho_maintenance'", [int(maintenance)])
|
||||
|
||||
def reload(self):
|
||||
# Reload settings from bancho_settings
|
||||
glob.banchoConf.loadSettings()
|
||||
|
||||
# Reload channels too
|
||||
glob.channels.loadChannels()
|
||||
|
||||
# And chat filters
|
||||
glob.chatFilters.loadFilters()
|
||||
|
||||
# Send new channels and new bottom icon to everyone
|
||||
glob.streams.broadcast("main", serverPackets.mainMenuIcon(glob.banchoConf.config["menuIcon"]))
|
||||
glob.streams.broadcast("main", serverPackets.channelInfoEnd())
|
||||
for key, value in glob.channels.channels.items():
|
||||
if value.publicRead == True and value.hidden == False:
|
||||
glob.streams.broadcast("main", serverPackets.channelInfo(key))
|
@@ -1,20 +1,16 @@
|
||||
from objects import glob
|
||||
|
||||
class channel:
|
||||
"""
|
||||
A chat channel
|
||||
"""
|
||||
|
||||
def __init__(self, name, description, publicRead, publicWrite, temp, hidden):
|
||||
"""
|
||||
Create a new chat channel object
|
||||
|
||||
name -- channel name
|
||||
description -- channel description
|
||||
publicRead -- bool, if true channel can be read by everyone, if false it can be read only by mods/admins
|
||||
publicWrite -- bool, same as public read but relative to write permissions
|
||||
temp -- if True, channel will be deleted when there's no one in the channel
|
||||
hidden -- if True, channel won't be shown in channels list
|
||||
:param name: channel name
|
||||
:param description: channel description
|
||||
:param publicRead: if True, this channel can be read by everyone. If False, it can be read only by mods/admins
|
||||
:param publicWrite: same as public read, but regards writing permissions
|
||||
:param temp: if True, this channel will be deleted when there's no one in this channel
|
||||
:param hidden: if True, thic channel won't be shown in channels list
|
||||
"""
|
||||
self.name = name
|
||||
self.description = description
|
||||
@@ -22,7 +18,6 @@ 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)
|
||||
@@ -32,41 +27,7 @@ class channel:
|
||||
elif self.name.startswith("#multi_"):
|
||||
self.clientName = "#multiplayer"
|
||||
|
||||
def userJoin(self, userID):
|
||||
"""
|
||||
Add a user to connected users
|
||||
|
||||
userID -- user ID that joined the channel
|
||||
"""
|
||||
if userID not in self.connectedUsers:
|
||||
self.connectedUsers.append(userID)
|
||||
|
||||
def userPart(self, userID):
|
||||
"""
|
||||
Remove a user from connected users
|
||||
|
||||
userID -- user ID that left the channel
|
||||
"""
|
||||
if userID in self.connectedUsers:
|
||||
self.connectedUsers.remove(userID)
|
||||
|
||||
# Remove temp channels if empty or there's only fokabot connected
|
||||
l = len(self.connectedUsers)
|
||||
if self.temp == True and ((l == 0) or (l == 1 and 999 in self.connectedUsers)):
|
||||
glob.channels.removeChannel(self.name)
|
||||
|
||||
def getConnectedUsers(self):
|
||||
"""
|
||||
Get connected user IDs list
|
||||
|
||||
return -- connectedUsers list
|
||||
"""
|
||||
return self.connectedUsers
|
||||
|
||||
def getConnectedUsersCount(self):
|
||||
"""
|
||||
Count connected users
|
||||
|
||||
return -- connected users number
|
||||
"""
|
||||
return len(self.connectedUsers)
|
||||
# Make Foka join the channel
|
||||
fokaToken = glob.tokens.getTokenFromUserID(999)
|
||||
if fokaToken is not None:
|
||||
fokaToken.joinChannel(self)
|
@@ -1,21 +1,18 @@
|
||||
from common.log import logUtils as log
|
||||
from objects import channel
|
||||
from objects import glob
|
||||
from helpers import chatHelper as chat
|
||||
|
||||
|
||||
class channelList:
|
||||
"""
|
||||
Channel list
|
||||
|
||||
channels -- dictionary. key: channel name, value: channel object
|
||||
"""
|
||||
channels = {}
|
||||
def __init__(self):
|
||||
self.channels = {}
|
||||
|
||||
def loadChannels(self):
|
||||
"""
|
||||
Load chat channels from db and add them to channels dictionary
|
||||
Load chat channels from db and add them to channels list
|
||||
:return:
|
||||
"""
|
||||
|
||||
# Get channels from DB
|
||||
channels = glob.db.fetchAll("SELECT * FROM bancho_channels")
|
||||
|
||||
@@ -28,15 +25,17 @@ class channelList:
|
||||
|
||||
def addChannel(self, name, description, publicRead, publicWrite, temp = False, hidden = False):
|
||||
"""
|
||||
Add a channel object to channels dictionary
|
||||
Add a channel to channels list
|
||||
|
||||
name -- channel name
|
||||
description -- channel description
|
||||
publicRead -- bool, if true channel can be read by everyone, if false it can be read only by mods/admins
|
||||
publicWrite -- bool, same as public read but relative to write permissions
|
||||
temp -- if True, channel will be deleted when there's no one in the channel. Optional. Default = False.
|
||||
hidden -- if True, channel will be hidden in channels list. Optional. Default = False.
|
||||
:param name: channel name
|
||||
:param description: channel description
|
||||
:param publicRead: if True, this channel can be read by everyone. If False, it can be read only by mods/admins
|
||||
:param publicWrite: same as public read, but regards writing permissions
|
||||
:param temp: if True, this channel will be deleted when there's no one in this channel
|
||||
: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))
|
||||
|
||||
@@ -45,11 +44,12 @@ class channelList:
|
||||
Add a temporary channel (like #spectator or #multiplayer), gets deleted when there's no one in the channel
|
||||
and it's hidden in channels list
|
||||
|
||||
name -- channel name
|
||||
return -- True if channel was created, False if failed
|
||||
: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, True, True)
|
||||
log.info("Created temp channel {}".format(name))
|
||||
|
||||
@@ -57,10 +57,19 @@ class channelList:
|
||||
"""
|
||||
Removes a channel from channels list
|
||||
|
||||
name -- channel name
|
||||
:param name: channel name
|
||||
:return:
|
||||
"""
|
||||
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))
|
||||
|
@@ -1,9 +1,20 @@
|
||||
class chatFilters:
|
||||
def __init__(self, fileName="filters.txt"):
|
||||
"""
|
||||
Initialize chat filters
|
||||
|
||||
:param fileName: name of the file containing filters. Default: filters.txt
|
||||
"""
|
||||
self.filters = {}
|
||||
self.loadFilters(fileName)
|
||||
|
||||
def loadFilters(self, fileName="filters.txt"):
|
||||
"""
|
||||
Load filters from a file
|
||||
|
||||
:param fileName: name of the file containing filters. Default: filters.txt
|
||||
:return:
|
||||
"""
|
||||
# Reset chat filters
|
||||
self.filters = {}
|
||||
|
||||
@@ -19,6 +30,12 @@ class chatFilters:
|
||||
self.filters[lineSplit[0].lower()] = lineSplit[1].replace("\n", "")
|
||||
|
||||
def filterMessage(self, message):
|
||||
"""
|
||||
Replace forbidden words with filtered ones
|
||||
|
||||
:param message: normal message
|
||||
:return: filtered message
|
||||
"""
|
||||
# Split words by spaces
|
||||
messageTemp = message.split(" ")
|
||||
|
||||
|
@@ -12,29 +12,35 @@ from objects import glob
|
||||
npRegex = re.compile("^https?:\\/\\/osu\\.ppy\\.sh\\/b\\/(\\d*)")
|
||||
|
||||
def connect():
|
||||
"""Add FokaBot to connected users and send userpanel/stats packet to everyone"""
|
||||
"""
|
||||
Connect FokaBot to Bancho
|
||||
|
||||
:return:
|
||||
"""
|
||||
token = glob.tokens.addToken(999)
|
||||
token.actionID = actions.IDLE
|
||||
glob.streams.broadcast("main", serverPackets.userPanel(999))
|
||||
glob.streams.broadcast("main", serverPackets.userStats(999))
|
||||
|
||||
def disconnect():
|
||||
"""Remove FokaBot from connected users"""
|
||||
"""
|
||||
Disconnect FokaBot from Bancho
|
||||
|
||||
:return:
|
||||
"""
|
||||
glob.tokens.deleteToken(glob.tokens.getTokenFromUserID(999))
|
||||
|
||||
def fokabotResponse(fro, chan, message):
|
||||
"""
|
||||
Check if a message has triggered fokabot (and return its response)
|
||||
Check if a message has triggered FokaBot
|
||||
|
||||
fro -- sender username (for permissions stuff with admin commands)
|
||||
chan -- channel name
|
||||
message -- message
|
||||
|
||||
return -- fokabot's response string or False
|
||||
:param fro: sender username
|
||||
:param chan: channel name (or receiver username)
|
||||
:param message: chat mesage
|
||||
:return: FokaBot's response or False if no response
|
||||
"""
|
||||
for i in fokabotCommands.commands:
|
||||
# Loop though all commands
|
||||
#if i["trigger"] in message:
|
||||
if generalUtils.strContains(message, i["trigger"]):
|
||||
# message has triggered a command
|
||||
|
||||
|
@@ -1,7 +1,6 @@
|
||||
"""Global objects and variables"""
|
||||
|
||||
import time
|
||||
|
||||
from common.ddog import datadogClient
|
||||
from common.files import fileBuffer, fileLocks
|
||||
from objects import channelList
|
||||
@@ -12,28 +11,27 @@ from common.web import schiavo
|
||||
|
||||
try:
|
||||
with open("version") as f:
|
||||
VERSION = f.read()
|
||||
VERSION = f.read().strip()
|
||||
if VERSION == "":
|
||||
raise
|
||||
raise Exception
|
||||
except:
|
||||
VERSION = "¯\_(xd)_/¯"
|
||||
VERSION = "Unknown"
|
||||
|
||||
DATADOG_PREFIX = "peppy"
|
||||
application = None
|
||||
db = None
|
||||
redis = None
|
||||
conf = None
|
||||
banchoConf = None
|
||||
tokens = tokenList.tokenList()
|
||||
channels = channelList.channelList()
|
||||
matches = matchList.matchList()
|
||||
restarting = False
|
||||
fLocks = fileLocks.fileLocks()
|
||||
fileBuffers = fileBuffer.buffersList()
|
||||
schiavo = schiavo.schiavo()
|
||||
dog = datadogClient.datadogClient()
|
||||
verifiedCache = {}
|
||||
chatFilters = None
|
||||
userIDCache = {}
|
||||
pool = None
|
||||
ircServer = None
|
||||
busyThreads = 0
|
||||
@@ -45,8 +43,8 @@ gzip = False
|
||||
localize = False
|
||||
sentry = False
|
||||
irc = False
|
||||
restarting = False
|
||||
|
||||
startTime = int(time.time())
|
||||
|
||||
|
||||
streams = streamList.streamList()
|
||||
|
193
objects/match.py
193
objects/match.py
@@ -13,7 +13,7 @@ from objects import glob
|
||||
|
||||
class slot:
|
||||
def __init__(self):
|
||||
self.status = slotStatuses.free
|
||||
self.status = slotStatuses.FREE
|
||||
self.team = 0
|
||||
self.userID = -1
|
||||
self.user = None
|
||||
@@ -23,19 +23,18 @@ class slot:
|
||||
self.complete = False
|
||||
|
||||
class match:
|
||||
"""Multiplayer match object"""
|
||||
def __init__(self, matchID, matchName, matchPassword, beatmapID, beatmapName, beatmapMD5, gameMode, hostUserID):
|
||||
"""
|
||||
Create a new match object
|
||||
|
||||
matchID -- match progressive identifier
|
||||
matchName -- match name, string
|
||||
matchPassword -- match md5 password. Leave empty for no password
|
||||
beatmapID -- beatmap ID
|
||||
beatmapName -- beatmap name, string
|
||||
beatmapMD5 -- beatmap md5 hash, string
|
||||
gameMode -- game mode ID. See gameModes.py
|
||||
hostUserID -- user id of the host
|
||||
:param matchID: match progressive identifier
|
||||
:param matchName: match name, string
|
||||
:param matchPassword: match md5 password. Leave empty for no password
|
||||
:param beatmapID: beatmap ID
|
||||
:param beatmapName: beatmap name, string
|
||||
:param beatmapMD5: beatmap md5 hash, string
|
||||
:param gameMode: game mode ID. See gameModes.py
|
||||
:param hostUserID: user id of the host
|
||||
"""
|
||||
self.matchID = matchID
|
||||
self.streamName = "multi/{}".format(self.matchID)
|
||||
@@ -49,9 +48,9 @@ class match:
|
||||
self.beatmapMD5 = beatmapMD5
|
||||
self.hostUserID = hostUserID
|
||||
self.gameMode = gameMode
|
||||
self.matchScoringType = matchScoringTypes.score # default values
|
||||
self.matchTeamType = matchTeamTypes.headToHead # default value
|
||||
self.matchModMode = matchModModes.normal # default value
|
||||
self.matchScoringType = matchScoringTypes.SCORE # default values
|
||||
self.matchTeamType = matchTeamTypes.HEAD_TO_HEAD # default value
|
||||
self.matchModMode = matchModModes.NORMAL # default value
|
||||
self.seed = 0
|
||||
self.matchDataCache = bytes()
|
||||
|
||||
@@ -70,6 +69,8 @@ class match:
|
||||
def getMatchData(self):
|
||||
"""
|
||||
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
|
||||
@@ -109,7 +110,7 @@ class match:
|
||||
])
|
||||
|
||||
# Slot mods if free mod is enabled
|
||||
if safeMatch.matchModMode == matchModModes.freeMod:
|
||||
if safeMatch.matchModMode == matchModModes.FREE_MOD:
|
||||
for i in range(0,16):
|
||||
struct.append([safeMatch.slots[i].mods, dataTypes.UINT32])
|
||||
|
||||
@@ -123,7 +124,8 @@ class match:
|
||||
"""
|
||||
Set room host to newHost and send him host packet
|
||||
|
||||
newHost -- new host userID
|
||||
:param newHost: new host userID
|
||||
:return:
|
||||
"""
|
||||
slotID = self.getUserSlotID(newHost)
|
||||
if slotID is None or self.slots[slotID].user not in glob.tokens.tokens:
|
||||
@@ -135,7 +137,21 @@ class match:
|
||||
log.info("MPROOM{}: {} is now the host".format(self.matchID, token.username))
|
||||
|
||||
def setSlot(self, slotID, status = None, team = None, user = "", mods = None, loaded = None, skip = None, complete = None):
|
||||
#self.setSlot(i, slotStatuses.notReady, 0, user, 0)
|
||||
"""
|
||||
Set data for a specific slot.
|
||||
All fields but slotID are optional.
|
||||
Skipped fields won't be edited.
|
||||
|
||||
:param slotID: slot ID
|
||||
:param status: new status
|
||||
:param team: new team
|
||||
:param user: new user id
|
||||
:param mods: new mods
|
||||
:param loaded: new loaded status
|
||||
:param skip: new skip value
|
||||
:param complete: new completed value
|
||||
:return:
|
||||
"""
|
||||
if status is not None:
|
||||
self.slots[slotID].status = status
|
||||
|
||||
@@ -161,8 +177,9 @@ class match:
|
||||
"""
|
||||
Set slotID mods. Same as calling setSlot and then sendUpdate
|
||||
|
||||
slotID -- slot number
|
||||
mods -- new mods
|
||||
:param slotID: slot number
|
||||
:param mods: new mods
|
||||
:return:
|
||||
"""
|
||||
# Set new slot data and send update
|
||||
self.setSlot(slotID, mods=mods)
|
||||
@@ -174,14 +191,15 @@ class match:
|
||||
Switch slotID ready/not ready status
|
||||
Same as calling setSlot and then sendUpdate
|
||||
|
||||
slotID -- slot number
|
||||
:param slotID: slot number
|
||||
:return:
|
||||
"""
|
||||
# Update ready status and setnd update
|
||||
oldStatus = self.slots[slotID].status
|
||||
if oldStatus == slotStatuses.ready:
|
||||
newStatus = slotStatuses.notReady
|
||||
if oldStatus == slotStatuses.READY:
|
||||
newStatus = slotStatuses.NOT_READY
|
||||
else:
|
||||
newStatus = slotStatuses.ready
|
||||
newStatus = slotStatuses.READY
|
||||
self.setSlot(slotID, newStatus)
|
||||
self.sendUpdates()
|
||||
log.info("MPROOM{}: Slot{} changed ready status to {}".format(self.matchID, slotID, self.slots[slotID].status))
|
||||
@@ -191,13 +209,14 @@ class match:
|
||||
Lock a slot
|
||||
Same as calling setSlot and then sendUpdate
|
||||
|
||||
slotID -- slot number
|
||||
:param slotID: slot number
|
||||
:return:
|
||||
"""
|
||||
# Check if slot is already locked
|
||||
if self.slots[slotID].status == slotStatuses.locked:
|
||||
newStatus = slotStatuses.free
|
||||
if self.slots[slotID].status == slotStatuses.LOCKED:
|
||||
newStatus = slotStatuses.FREE
|
||||
else:
|
||||
newStatus = slotStatuses.locked
|
||||
newStatus = slotStatuses.LOCKED
|
||||
|
||||
# Send updated settings to kicked user, so he returns to lobby
|
||||
if self.slots[slotID].user is not None and self.slots[slotID].user in glob.tokens.tokens:
|
||||
@@ -208,13 +227,14 @@ class match:
|
||||
|
||||
# Send updates to everyone else
|
||||
self.sendUpdates()
|
||||
log.info("MPROOM{}: Slot{} {}".format(self.matchID, slotID, "locked" if newStatus == slotStatuses.locked else "unlocked"))
|
||||
log.info("MPROOM{}: Slot{} {}".format(self.matchID, slotID, "locked" if newStatus == slotStatuses.LOCKED else "unlocked"))
|
||||
|
||||
def playerLoaded(self, userID):
|
||||
"""
|
||||
Set a player loaded status to True
|
||||
|
||||
userID -- ID of user
|
||||
:param userID: ID of user
|
||||
:return:
|
||||
"""
|
||||
slotID = self.getUserSlotID(userID)
|
||||
if slotID is None:
|
||||
@@ -228,7 +248,7 @@ class match:
|
||||
total = 0
|
||||
loaded = 0
|
||||
for i in range(0,16):
|
||||
if self.slots[i].status == slotStatuses.playing:
|
||||
if self.slots[i].status == slotStatuses.PLAYING:
|
||||
total+=1
|
||||
if self.slots[i].loaded:
|
||||
loaded+=1
|
||||
@@ -237,7 +257,11 @@ class match:
|
||||
self.allPlayersLoaded()
|
||||
|
||||
def allPlayersLoaded(self):
|
||||
"""Send allPlayersLoaded packet to every playing usr in match"""
|
||||
"""
|
||||
Send allPlayersLoaded packet to every playing usr in match
|
||||
|
||||
:return:
|
||||
"""
|
||||
glob.streams.broadcast(self.playingStreamName, serverPackets.allPlayersLoaded())
|
||||
log.info("MPROOM{}: All players loaded! Match starting...".format(self.matchID))
|
||||
|
||||
@@ -245,7 +269,8 @@ class match:
|
||||
"""
|
||||
Set a player skip status to True
|
||||
|
||||
userID -- ID of user
|
||||
:param userID: ID of user
|
||||
:return:
|
||||
"""
|
||||
slotID = self.getUserSlotID(userID)
|
||||
if slotID is None:
|
||||
@@ -263,7 +288,7 @@ class match:
|
||||
total = 0
|
||||
skipped = 0
|
||||
for i in range(0,16):
|
||||
if self.slots[i].status == slotStatuses.playing:
|
||||
if self.slots[i].status == slotStatuses.PLAYING:
|
||||
total+=1
|
||||
if self.slots[i].skip:
|
||||
skipped+=1
|
||||
@@ -272,7 +297,11 @@ class match:
|
||||
self.allPlayersSkipped()
|
||||
|
||||
def allPlayersSkipped(self):
|
||||
"""Send allPlayersSkipped packet to every playing usr in match"""
|
||||
"""
|
||||
Send allPlayersSkipped packet to every playing usr in match
|
||||
|
||||
:return:
|
||||
"""
|
||||
glob.streams.broadcast(self.playingStreamName, serverPackets.allPlayersSkipped())
|
||||
log.info("MPROOM{}: All players have skipped!".format(self.matchID))
|
||||
|
||||
@@ -280,7 +309,7 @@ class match:
|
||||
"""
|
||||
Set userID's slot completed to True
|
||||
|
||||
userID -- ID of user
|
||||
:param userID: ID of user
|
||||
"""
|
||||
slotID = self.getUserSlotID(userID)
|
||||
if slotID is None:
|
||||
@@ -294,7 +323,7 @@ class match:
|
||||
total = 0
|
||||
completed = 0
|
||||
for i in range(0,16):
|
||||
if self.slots[i].status == slotStatuses.playing:
|
||||
if self.slots[i].status == slotStatuses.PLAYING:
|
||||
total+=1
|
||||
if self.slots[i].complete:
|
||||
completed+=1
|
||||
@@ -303,15 +332,18 @@ class match:
|
||||
self.allPlayersCompleted()
|
||||
|
||||
def allPlayersCompleted(self):
|
||||
"""Cleanup match stuff and send match end packet to everyone"""
|
||||
"""
|
||||
Cleanup match stuff and send match end packet to everyone
|
||||
|
||||
:return:
|
||||
"""
|
||||
# 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.notReady
|
||||
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
|
||||
@@ -323,6 +355,7 @@ class match:
|
||||
glob.streams.broadcast(self.streamName, serverPackets.matchComplete())
|
||||
|
||||
# Destroy playing stream
|
||||
glob.streams.dispose(self.playingStreamName)
|
||||
glob.streams.remove(self.playingStreamName)
|
||||
|
||||
# Console output
|
||||
@@ -332,7 +365,7 @@ class match:
|
||||
"""
|
||||
Get slot ID occupied by userID
|
||||
|
||||
return -- slot id if found, None if user is not in room
|
||||
:return: slot id if found, None if user is not in room
|
||||
"""
|
||||
for i in range(0,16):
|
||||
if self.slots[i].user is not None and self.slots[i].user in glob.tokens.tokens and glob.tokens.tokens[self.slots[i].user].userID == userID:
|
||||
@@ -343,21 +376,20 @@ class match:
|
||||
"""
|
||||
Add someone to users in match
|
||||
|
||||
userID -- user id of the user
|
||||
return -- True if join success, False if fail (room is full)
|
||||
: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
|
||||
for i in range(0,16):
|
||||
if self.slots[i].user == user.token:
|
||||
# Set bugged slot to free
|
||||
self.setSlot(i, slotStatuses.free, 0, None, 0)
|
||||
self.setSlot(i, slotStatuses.FREE, 0, None, 0)
|
||||
|
||||
# Find first free slot
|
||||
for i in range(0,16):
|
||||
if self.slots[i].status == slotStatuses.free:
|
||||
if self.slots[i].status == slotStatuses.FREE:
|
||||
# Occupy slot
|
||||
self.setSlot(i, slotStatuses.notReady, 0, user.token, 0)
|
||||
self.setSlot(i, slotStatuses.NOT_READY, 0, user.token, 0)
|
||||
|
||||
# Send updated match data
|
||||
self.sendUpdates()
|
||||
@@ -372,7 +404,8 @@ class match:
|
||||
"""
|
||||
Remove someone from users in match
|
||||
|
||||
userID -- user if of the user
|
||||
:param user: user object of the user
|
||||
:return:
|
||||
"""
|
||||
# Make sure the user is in room
|
||||
slotID = self.getUserSlotID(user.userID)
|
||||
@@ -380,7 +413,7 @@ class match:
|
||||
return
|
||||
|
||||
# Set that slot to free
|
||||
self.setSlot(slotID, slotStatuses.free, 0, None, 0)
|
||||
self.setSlot(slotID, slotStatuses.FREE, 0, None, 0)
|
||||
|
||||
# Check if everyone left
|
||||
if self.countUsers() == 0:
|
||||
@@ -407,8 +440,9 @@ class match:
|
||||
"""
|
||||
Change userID slot to newSlotID
|
||||
|
||||
userID -- user that changed slot
|
||||
newSlotID -- slot id of new slot
|
||||
:param userID: user that changed slot
|
||||
:param newSlotID: slot id of new slot
|
||||
:return:
|
||||
"""
|
||||
# Make sure the user is in room
|
||||
oldSlotID = self.getUserSlotID(userID)
|
||||
@@ -416,7 +450,7 @@ class match:
|
||||
return
|
||||
|
||||
# Make sure there is no one inside new slot
|
||||
if self.slots[newSlotID].user is not None and self.slots[newSlotID].status != slotStatuses.free:
|
||||
if self.slots[newSlotID].user is not None and self.slots[newSlotID].status != slotStatuses.FREE:
|
||||
return
|
||||
|
||||
# Get old slot data
|
||||
@@ -424,7 +458,7 @@ class match:
|
||||
oldData = copy.deepcopy(self.slots[oldSlotID])
|
||||
|
||||
# Free old slot
|
||||
self.setSlot(oldSlotID, slotStatuses.free, 0, None, 0, False, False, False)
|
||||
self.setSlot(oldSlotID, slotStatuses.FREE, 0, None, 0, False, False, False)
|
||||
|
||||
# Occupy new slot
|
||||
self.setSlot(newSlotID, oldData.status, oldData.team, oldData.user, oldData.mods)
|
||||
@@ -439,13 +473,10 @@ class match:
|
||||
"""
|
||||
Change match password to newPassword
|
||||
|
||||
newPassword -- new password string
|
||||
:param newPassword: new password string
|
||||
:return:
|
||||
"""
|
||||
self.matchPassword = newPassword
|
||||
#if newPassword != "":
|
||||
# self.matchPassword = generalUtils.stringMd5(newPassword)
|
||||
#else:
|
||||
# self.matchPassword = ""
|
||||
|
||||
# Send password change to every user in match
|
||||
glob.streams.broadcast(self.streamName, serverPackets.changeMatchPassword(self.matchPassword))
|
||||
@@ -460,7 +491,8 @@ class match:
|
||||
"""
|
||||
Set match global mods
|
||||
|
||||
mods -- mods bitwise int thing
|
||||
:param mods: mods bitwise int thing
|
||||
:return:
|
||||
"""
|
||||
# Set new mods and send update
|
||||
self.mods = mods
|
||||
@@ -471,8 +503,9 @@ class match:
|
||||
"""
|
||||
Set no beatmap status for userID
|
||||
|
||||
userID -- ID of user
|
||||
has -- True if has beatmap, false if not
|
||||
:param userID: ID of user
|
||||
:param has: True if has beatmap, false if not
|
||||
:return:
|
||||
"""
|
||||
# Make sure the user is in room
|
||||
slotID = self.getUserSlotID(userID)
|
||||
@@ -480,7 +513,7 @@ class match:
|
||||
return
|
||||
|
||||
# Set slot
|
||||
self.setSlot(slotID, slotStatuses.noMap if not has else slotStatuses.notReady)
|
||||
self.setSlot(slotID, slotStatuses.NO_MAP if not has else slotStatuses.NOT_READY)
|
||||
|
||||
# Send updates
|
||||
self.sendUpdates()
|
||||
@@ -489,7 +522,8 @@ class match:
|
||||
"""
|
||||
Transfer host to slotID
|
||||
|
||||
slotID -- ID of slot
|
||||
:param slotID: ID of slot
|
||||
:return:
|
||||
"""
|
||||
# Make sure there is someone in that slot
|
||||
if self.slots[slotID].user is None or self.slots[slotID].user not in glob.tokens.tokens:
|
||||
@@ -505,7 +539,8 @@ class match:
|
||||
"""
|
||||
Send userID's failed packet to everyone in match
|
||||
|
||||
userID -- ID of user
|
||||
:param userID: ID of user
|
||||
:return:
|
||||
"""
|
||||
# Make sure the user is in room
|
||||
slotID = self.getUserSlotID(userID)
|
||||
@@ -522,10 +557,10 @@ class match:
|
||||
"""
|
||||
Fro invites to in this match.
|
||||
|
||||
fro -- sender userID
|
||||
to -- receiver userID
|
||||
:param fro: sender userID
|
||||
:param to: receiver userID
|
||||
:return:
|
||||
"""
|
||||
|
||||
# Get tokens
|
||||
froToken = glob.tokens.getTokenFromUserID(fro)
|
||||
toToken = glob.tokens.getTokenFromUserID(to)
|
||||
@@ -544,7 +579,7 @@ class match:
|
||||
"""
|
||||
Return how many players are in that match
|
||||
|
||||
return -- number of users
|
||||
:return: number of users
|
||||
"""
|
||||
c = 0
|
||||
for i in range(0,16):
|
||||
@@ -556,7 +591,8 @@ class match:
|
||||
"""
|
||||
Change userID's team
|
||||
|
||||
userID -- id of user
|
||||
:param userID: id of user
|
||||
:return:
|
||||
"""
|
||||
# Make sure the user is in room
|
||||
slotID = self.getUserSlotID(userID)
|
||||
@@ -564,11 +600,16 @@ class match:
|
||||
return
|
||||
|
||||
# Update slot and send update
|
||||
newTeam = matchTeams.blue if self.slots[slotID].team == matchTeams.red else matchTeams.red
|
||||
newTeam = matchTeams.BLUE if self.slots[slotID].team == matchTeams.RED else matchTeams.RED
|
||||
self.setSlot(slotID, None, newTeam)
|
||||
self.sendUpdates()
|
||||
|
||||
def sendUpdates(self):
|
||||
"""
|
||||
Send match updates packet to everyone in lobby and room streams
|
||||
|
||||
:return:
|
||||
"""
|
||||
self.matchDataCache = serverPackets.updateMatch(self.matchID)
|
||||
if self.matchDataCache is not None:
|
||||
glob.streams.broadcast(self.streamName, self.matchDataCache)
|
||||
@@ -580,16 +621,17 @@ class match:
|
||||
"""
|
||||
Check if match teams are valid
|
||||
|
||||
return -- True if valid, False if invalid
|
||||
:return: True if valid, False if invalid
|
||||
:return:
|
||||
"""
|
||||
if self.matchTeamType != matchTeamTypes.teamVs or self.matchTeamType != matchTeamTypes.tagTeamVs:
|
||||
if self.matchTeamType != matchTeamTypes.TEAM_VS or self.matchTeamType != matchTeamTypes.TAG_TEAM_VS:
|
||||
# Teams are always valid if we have no teams
|
||||
return True
|
||||
|
||||
# We have teams, check if they are valid
|
||||
firstTeam = -1
|
||||
for i in range(0,16):
|
||||
if self.slots[i].user is not None and (self.slots[i].status & slotStatuses.noMap) == 0:
|
||||
if self.slots[i].user is not None and (self.slots[i].status & slotStatuses.NO_MAP) == 0:
|
||||
if firstTeam == -1:
|
||||
firstTeam = self.slots[i].team
|
||||
elif firstTeam != self.slots[i].team:
|
||||
@@ -600,6 +642,11 @@ class match:
|
||||
return False
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Start the match
|
||||
|
||||
:return:
|
||||
"""
|
||||
# Make sure we have enough players
|
||||
if self.countUsers() < 2 or not self.checkTeams():
|
||||
return
|
||||
@@ -613,8 +660,8 @@ class match:
|
||||
# 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:
|
||||
self.slots[i].status = slotStatuses.playing
|
||||
if (self.slots[i].status & slotStatuses.READY) > 0 and self.slots[i].user in glob.tokens.tokens:
|
||||
self.slots[i].status = slotStatuses.PLAYING
|
||||
self.slots[i].loaded = False
|
||||
self.slots[i].skip = False
|
||||
self.slots[i].complete = False
|
||||
|
@@ -1,7 +1,6 @@
|
||||
from objects import match
|
||||
from objects import glob
|
||||
from constants import serverPackets
|
||||
from common.log import logUtils as log
|
||||
|
||||
class matchList:
|
||||
def __init__(self):
|
||||
@@ -13,14 +12,14 @@ class matchList:
|
||||
"""
|
||||
Add a new match to matches list
|
||||
|
||||
matchName -- match name, string
|
||||
matchPassword -- match md5 password. Leave empty for no password
|
||||
beatmapID -- beatmap ID
|
||||
beatmapName -- beatmap name, string
|
||||
beatmapMD5 -- beatmap md5 hash, string
|
||||
gameMode -- game mode ID. See gameModes.py
|
||||
hostUserID -- user id of who created the match
|
||||
return -- match ID
|
||||
:param matchName: match name, string
|
||||
:param matchPassword: match md5 password. Leave empty for no password
|
||||
:param beatmapID: beatmap ID
|
||||
:param beatmapName: beatmap name, string
|
||||
:param beatmapMD5: beatmap md5 hash, string
|
||||
:param gameMode: game mode ID. See gameModes.py
|
||||
:param hostUserID: user id of who created the match
|
||||
:return: match ID
|
||||
"""
|
||||
# Add a new match to matches list and create its stream
|
||||
matchID = self.lastID
|
||||
@@ -32,16 +31,19 @@ class matchList:
|
||||
"""
|
||||
Destroy match object with id = matchID
|
||||
|
||||
matchID -- ID of match to dispose
|
||||
:param matchID: ID of match to dispose
|
||||
:return:
|
||||
"""
|
||||
# Make sure the match exists
|
||||
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)
|
||||
_match = self.matches.pop(matchID)
|
||||
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))
|
@@ -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
|
||||
@@ -12,24 +13,26 @@ from objects import glob
|
||||
|
||||
|
||||
class token:
|
||||
|
||||
def __init__(self, userID, token_ = None, ip ="", irc = False, timeOffset = 0, tournament = False):
|
||||
"""
|
||||
Create a token object and set userID and token
|
||||
|
||||
userID -- user associated to this token
|
||||
token -- if passed, set token to that value
|
||||
:param userID: user associated to this token
|
||||
:param token_: if passed, set token to that value
|
||||
if not passed, token will be generated
|
||||
ip -- client ip. optional.
|
||||
irc -- if True, set this token as IRC client. optional.
|
||||
timeOffset -- the time offset from UTC for this user. optional.
|
||||
:param ip: client ip. optional.
|
||||
:param irc: if True, set this token as IRC client. Default: False.
|
||||
: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.
|
||||
"""
|
||||
# Set stuff
|
||||
self.userID = userID
|
||||
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.irc = irc
|
||||
self.kicked = False
|
||||
self.restricted = userUtils.isRestricted(self.userID)
|
||||
self.loginTime = int(time.time())
|
||||
self.pingTime = self.loginTime
|
||||
@@ -37,6 +40,7 @@ class token:
|
||||
self.lock = threading.Lock() # Sync primitive
|
||||
self.streams = []
|
||||
self.tournament = tournament
|
||||
self.messagesBuffer = []
|
||||
|
||||
# Default variables
|
||||
self.spectators = []
|
||||
@@ -51,6 +55,7 @@ class token:
|
||||
self.country = 0
|
||||
self.location = [0,0]
|
||||
self.awayMessage = ""
|
||||
self.sentAway = []
|
||||
self.matchID = -1
|
||||
self.tillerino = [0,0,-1.0] # beatmap, mods, acc
|
||||
self.silenceEndTime = 0
|
||||
@@ -93,51 +98,62 @@ class token:
|
||||
"""
|
||||
Add bytes (packets) to queue
|
||||
|
||||
bytes -- (packet) bytes to enqueue
|
||||
:param bytes_: (packet) bytes to enqueue
|
||||
"""
|
||||
if not self.irc:
|
||||
|
||||
# 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))
|
||||
|
||||
|
||||
def resetQueue(self):
|
||||
"""Resets the queue. Call when enqueued packets have been sent"""
|
||||
self.queue = bytes()
|
||||
|
||||
|
||||
def joinChannel(self, channel):
|
||||
def joinChannel(self, channelObject):
|
||||
"""
|
||||
Add channel to joined channels list
|
||||
Join a channel
|
||||
|
||||
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 channelObject.publicRead == False and self.admin == False:
|
||||
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
|
||||
|
||||
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, location):
|
||||
def setLocation(self, latitude, longitude):
|
||||
"""
|
||||
Set location (latitude and longitude)
|
||||
Set client location
|
||||
|
||||
location -- [latitude, longitude]
|
||||
:param latitude: latitude
|
||||
:param longitude: longitude
|
||||
"""
|
||||
self.location = location
|
||||
self.location = (latitude, longitude)
|
||||
|
||||
def getLatitude(self):
|
||||
"""
|
||||
Get latitude
|
||||
|
||||
return -- latitude
|
||||
:return: latitude
|
||||
"""
|
||||
return self.location[0]
|
||||
|
||||
@@ -145,15 +161,16 @@ class token:
|
||||
"""
|
||||
Get longitude
|
||||
|
||||
return -- longitude
|
||||
:return: longitude
|
||||
"""
|
||||
return self.location[1]
|
||||
|
||||
def startSpectating(self, host):
|
||||
"""
|
||||
Set the spectating user to userID
|
||||
Set the spectating user to userID, join spectator stream and chat channel
|
||||
and send required packets to host
|
||||
|
||||
user -- user object
|
||||
:param host: host osuToken object
|
||||
"""
|
||||
# Stop spectating old client
|
||||
self.stopSpectating()
|
||||
@@ -193,6 +210,12 @@ class token:
|
||||
log.info("{} is spectating {}".format(self.username, host.username))
|
||||
|
||||
def stopSpectating(self):
|
||||
"""
|
||||
Stop spectating, leave spectator stream and channel
|
||||
and send required packets to host
|
||||
|
||||
:return:
|
||||
"""
|
||||
# Remove our userID from host's spectators
|
||||
if self.spectating is None:
|
||||
return
|
||||
@@ -221,46 +244,30 @@ class token:
|
||||
chat.partChannel(token=hostToken, channel="#spect_{}".format(hostToken.userID), kick=True)
|
||||
hostToken.leaveStream(streamName)
|
||||
|
||||
# Console output
|
||||
log.info("{} is no longer spectating {}. Current spectators: {}".format(self.username, self.spectatingUserID, hostToken.spectators))
|
||||
|
||||
# Part #spectator channel
|
||||
chat.partChannel(token=self, channel="#spect_{}".format(self.spectatingUserID), kick=True)
|
||||
|
||||
# Console output
|
||||
log.info("{} is no longer spectating {}".format(self.username, self.spectatingUserID))
|
||||
|
||||
# Set our spectating user to 0
|
||||
self.spectating = None
|
||||
self.spectatingUserID = 0
|
||||
|
||||
def setCountry(self, countryID):
|
||||
"""
|
||||
Set country to countryID
|
||||
|
||||
countryID -- numeric country ID. See countryHelper.py
|
||||
"""
|
||||
self.country = countryID
|
||||
|
||||
def getCountry(self):
|
||||
"""
|
||||
Get numeric country ID
|
||||
|
||||
return -- numeric country ID. See countryHelper.py
|
||||
"""
|
||||
return self.country
|
||||
|
||||
def updatePingTime(self):
|
||||
"""Update latest ping time"""
|
||||
"""
|
||||
Update latest ping time to current time
|
||||
|
||||
:return:
|
||||
"""
|
||||
self.pingTime = int(time.time())
|
||||
|
||||
def setAwayMessage(self, __awayMessage):
|
||||
"""Set a new away message"""
|
||||
self.awayMessage = __awayMessage
|
||||
|
||||
|
||||
def joinMatch(self, matchID):
|
||||
"""
|
||||
Set match to matchID, join match stream and channel
|
||||
|
||||
matchID -- new match ID
|
||||
:param matchID: new match ID
|
||||
:return:
|
||||
"""
|
||||
# Make sure the match exists
|
||||
if matchID not in glob.matches.matches:
|
||||
@@ -320,7 +327,10 @@ class token:
|
||||
"""
|
||||
Kick this user from the server
|
||||
|
||||
message -- Notification message to send to this user. Optional.
|
||||
: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"
|
||||
:return:
|
||||
"""
|
||||
# Send packet to target
|
||||
log.info("{} has been disconnected. ({})".format(self.username, reason))
|
||||
@@ -329,21 +339,28 @@ class token:
|
||||
self.enqueue(serverPackets.loginFailed())
|
||||
|
||||
# Logout event
|
||||
logoutEvent.handle(self, None)
|
||||
logoutEvent.handle(self, deleteToken=self.irc)
|
||||
|
||||
def silence(self, seconds, reason, author = 999):
|
||||
def silence(self, seconds = None, reason = "", author = 999):
|
||||
"""
|
||||
Silences this user (db, packet and token)
|
||||
|
||||
seconds -- silence length in seconds
|
||||
reason -- silence reason
|
||||
author -- userID of who has silenced the target. Optional. Default: 999 (fokabot)
|
||||
:param seconds: silence length in seconds. If None, get it from db. Default: None
|
||||
:param reason: silence reason. Default: empty string
|
||||
:param author: userID of who has silenced the user. Default: 999 (FokaBot)
|
||||
:return:
|
||||
"""
|
||||
if seconds is None:
|
||||
# Get silence expire from db if needed
|
||||
seconds = max(0, userUtils.getSilenceEnd(self.userID) - int(time.time()))
|
||||
else:
|
||||
# Silence in db and token
|
||||
self.silenceEndTime = int(time.time())+seconds
|
||||
userUtils.silence(self.userID, seconds, reason, author)
|
||||
|
||||
# Send silence packet to target
|
||||
# Silence token
|
||||
self.silenceEndTime = int(time.time()) + seconds
|
||||
|
||||
# Send silence packet to user
|
||||
self.enqueue(serverPackets.silenceEndTime(seconds))
|
||||
|
||||
# Send silenced packet to everyone else
|
||||
@@ -353,7 +370,8 @@ class token:
|
||||
"""
|
||||
Silences the user if is spamming.
|
||||
|
||||
increaseSpamRate -- pass True if the user has sent a new message. Optional. Default: True
|
||||
:param increaseSpamRate: set to True if the user has sent a new message. Default: True
|
||||
:return:
|
||||
"""
|
||||
# Increase the spam rate if needed
|
||||
if increaseSpamRate:
|
||||
@@ -367,7 +385,7 @@ class token:
|
||||
"""
|
||||
Returns True if this user is silenced, otherwise False
|
||||
|
||||
return -- True/False
|
||||
:return: True if this user is silenced, otherwise False
|
||||
"""
|
||||
return self.silenceEndTime-int(time.time()) > 0
|
||||
|
||||
@@ -376,12 +394,16 @@ class token:
|
||||
Returns the seconds left for this user's silence
|
||||
(0 if user is not silenced)
|
||||
|
||||
return -- silence seconds left
|
||||
:return: silence seconds left (or 0)
|
||||
"""
|
||||
return max(0, self.silenceEndTime-int(time.time()))
|
||||
|
||||
def updateCachedStats(self):
|
||||
"""Update all cached stats for this token"""
|
||||
"""
|
||||
Update all cached stats for this token
|
||||
|
||||
:return:
|
||||
"""
|
||||
stats = userUtils.getUserStats(self.userID, self.gameMode)
|
||||
log.debug(str(stats))
|
||||
if stats is None:
|
||||
@@ -394,36 +416,110 @@ class token:
|
||||
self.gameRank = stats["gameRank"]
|
||||
self.pp = stats["pp"]
|
||||
|
||||
def checkRestricted(self, force=False):
|
||||
def checkRestricted(self):
|
||||
"""
|
||||
Check if this token is restricted. If so, send fokabot message
|
||||
|
||||
force -- If True, get restricted value from db.
|
||||
If false, get the cached one. Optional. Default: False
|
||||
:return:
|
||||
"""
|
||||
if force:
|
||||
oldRestricted = self.restricted
|
||||
self.restricted = userUtils.isRestricted(self.userID)
|
||||
if self.restricted:
|
||||
self.setRestricted()
|
||||
elif not self.restricted and oldRestricted != self.restricted:
|
||||
self.resetRestricted()
|
||||
|
||||
def checkBanned(self):
|
||||
"""
|
||||
Check if this user is banned. If so, disconnect it.
|
||||
|
||||
:return:
|
||||
"""
|
||||
if userUtils.isBanned(self.userID):
|
||||
self.enqueue(serverPackets.loginBanned())
|
||||
logoutEvent.handle(self, deleteToken=False)
|
||||
|
||||
|
||||
def setRestricted(self):
|
||||
"""
|
||||
Set this token as restricted, send FokaBot message to user
|
||||
and send offline packet to everyone
|
||||
|
||||
:return:
|
||||
"""
|
||||
self.restricted = True
|
||||
chat.sendMessage("FokaBot", self.username, "Your account is currently in restricted mode. Please visit ripple's website for more information.")
|
||||
|
||||
def resetRestricted(self):
|
||||
"""
|
||||
Send FokaBot message to alert the user that he has been unrestricted
|
||||
and he has to log in again.
|
||||
|
||||
:return:
|
||||
"""
|
||||
chat.sendMessage("FokaBot", self.username, "Your account has been unrestricted! Please log in again.")
|
||||
|
||||
def joinStream(self, name):
|
||||
"""
|
||||
Join a packet stream, or create it if the stream doesn't exist.
|
||||
|
||||
:param name: stream name
|
||||
:return:
|
||||
"""
|
||||
glob.streams.join(name, token=self.token)
|
||||
if name not in self.streams:
|
||||
self.streams.append(name)
|
||||
|
||||
def leaveStream(self, name):
|
||||
"""
|
||||
Leave a packets stream
|
||||
|
||||
:param name: stream name
|
||||
:return:
|
||||
"""
|
||||
glob.streams.leave(name, token=self.token)
|
||||
if name in self.streams:
|
||||
self.streams.remove(name)
|
||||
|
||||
def leaveAllStreams(self):
|
||||
"""
|
||||
Leave all joined packet streams
|
||||
|
||||
:return:
|
||||
"""
|
||||
for i in self.streams:
|
||||
self.leaveStream(i)
|
||||
|
||||
def awayCheck(self, userID):
|
||||
"""
|
||||
Returns True if userID doesn't know that we are away
|
||||
Returns False if we are not away or if userID already knows we are away
|
||||
|
||||
:param userID: original sender userID
|
||||
:return:
|
||||
"""
|
||||
if self.awayMessage == "" or userID in self.sentAway:
|
||||
return False
|
||||
self.sentAway.append(userID)
|
||||
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,6 +1,8 @@
|
||||
import threading
|
||||
import time
|
||||
|
||||
import redis
|
||||
|
||||
from common.ripple import userUtils
|
||||
from common.log import logUtils as log
|
||||
from constants import serverPackets
|
||||
@@ -8,52 +10,45 @@ from events import logoutEvent
|
||||
from objects import glob
|
||||
from objects import osuToken
|
||||
|
||||
|
||||
class tokenList:
|
||||
"""
|
||||
List of connected osu tokens
|
||||
|
||||
tokens -- dictionary. key: token string, value: token object
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialize a tokens list
|
||||
"""
|
||||
self.tokens = {}
|
||||
|
||||
def addToken(self, userID, ip = "", irc = False, timeOffset=0, tournament=False):
|
||||
"""
|
||||
Add a token object to tokens list
|
||||
|
||||
userID -- user id associated to that token
|
||||
irc -- if True, set this token as IRC client
|
||||
return -- token object
|
||||
: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.
|
||||
:return: token object
|
||||
"""
|
||||
newToken = osuToken.token(userID, ip=ip, irc=irc, timeOffset=timeOffset, tournament=tournament)
|
||||
self.tokens[newToken.token] = newToken
|
||||
glob.redis.incr("ripple:online_users")
|
||||
return newToken
|
||||
|
||||
def deleteToken(self, token):
|
||||
"""
|
||||
Delete a token from token list if it exists
|
||||
|
||||
token -- token string
|
||||
:param token: token string
|
||||
:return:
|
||||
"""
|
||||
if token in self.tokens:
|
||||
# Delete session from DB
|
||||
if self.tokens[token].ip != "":
|
||||
userUtils.deleteBanchoSessions(self.tokens[token].userID, self.tokens[token].ip)
|
||||
|
||||
# Pop token from list
|
||||
self.tokens.pop(token)
|
||||
glob.redis.decr("ripple:online_users")
|
||||
|
||||
def getUserIDFromToken(self, token):
|
||||
"""
|
||||
Get user ID from a token
|
||||
|
||||
token -- token to find
|
||||
return -- false if not found, userID if found
|
||||
:param token: token to find
|
||||
:return: False if not found, userID if found
|
||||
"""
|
||||
# Make sure the token exists
|
||||
if token not in self.tokens:
|
||||
@@ -62,63 +57,91 @@ 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
|
||||
|
||||
userID -- user ID to find
|
||||
return -- False if not found, token object if found
|
||||
: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
|
||||
if _all:
|
||||
ret.append(value)
|
||||
else:
|
||||
return value
|
||||
|
||||
# Return none if not found
|
||||
# Return full list or None if not found
|
||||
if _all:
|
||||
return ret
|
||||
else:
|
||||
return None
|
||||
|
||||
def getTokenFromUsername(self, username, ignoreIRC=False):
|
||||
def getTokenFromUsername(self, username, ignoreIRC=False, safe=False, _all=False):
|
||||
"""
|
||||
Get token from a username
|
||||
Get an osuToken object from an username
|
||||
|
||||
username -- username to find
|
||||
return -- False if not found, token object if found
|
||||
:param username: normal username or safe username
|
||||
: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()
|
||||
who = username.lower() if not safe else username
|
||||
|
||||
# Make sure the token exists
|
||||
ret = []
|
||||
for _, value in self.tokens.items():
|
||||
if value.username.lower() == who:
|
||||
if (not safe and value.username.lower() == who) or (safe and value.safeUsername == who):
|
||||
if ignoreIRC and value.irc:
|
||||
continue
|
||||
if _all:
|
||||
ret.append(value)
|
||||
else:
|
||||
return value
|
||||
|
||||
# Return none if not found
|
||||
# Return full list or None if not found
|
||||
if _all:
|
||||
return ret
|
||||
else:
|
||||
return None
|
||||
|
||||
def deleteOldTokens(self, userID):
|
||||
"""
|
||||
Delete old userID's tokens if found
|
||||
|
||||
userID -- tokens associated to this user will be deleted
|
||||
:param userID: tokens associated to this user will be deleted
|
||||
:return:
|
||||
"""
|
||||
|
||||
# Delete older tokens
|
||||
delete = []
|
||||
for key, value in list(self.tokens.items()):
|
||||
if value.userID == userID:
|
||||
# Delete this token from the dictionary
|
||||
self.tokens[key].kick("You have logged in from somewhere else. You can't connect to Bancho/IRC from more than one device at the same time.", "kicked, multiple clients")
|
||||
#self.tokens[key].kick("You have logged in from somewhere else. You can't connect to Bancho/IRC from more than one device at the same time.", "kicked, multiple clients")
|
||||
delete.append(self.tokens[key])
|
||||
|
||||
for i in delete:
|
||||
logoutEvent.handle(i)
|
||||
|
||||
def multipleEnqueue(self, packet, who, but = False):
|
||||
"""
|
||||
Enqueue a packet to multiple users
|
||||
|
||||
packet -- packet bytes to enqueue
|
||||
who -- userIDs array
|
||||
but -- if True, enqueue to everyone but users in who array
|
||||
:param packet: packet bytes to enqueue
|
||||
:param who: userIDs array
|
||||
:param but: if True, enqueue to everyone but users in `who` array
|
||||
:return:
|
||||
"""
|
||||
for _, value in self.tokens.items():
|
||||
shouldEnqueue = False
|
||||
@@ -134,19 +157,21 @@ class tokenList:
|
||||
"""
|
||||
Enqueue packet(s) to every connected user
|
||||
|
||||
packet -- packet bytes to enqueue
|
||||
:param packet: packet bytes to enqueue
|
||||
:return:
|
||||
"""
|
||||
for _, value in self.tokens.items():
|
||||
value.enqueue(packet)
|
||||
|
||||
def usersTimeoutCheckLoop(self, timeoutTime = 100, checkTime = 100):
|
||||
"""
|
||||
Deletes all timed out users.
|
||||
If called once, will recall after checkTime seconds and so on, forever
|
||||
Start timed out users disconnect loop.
|
||||
This function will be called every `checkTime` seconds and so on, forever.
|
||||
CALL THIS FUNCTION ONLY ONCE!
|
||||
|
||||
timeoutTime - seconds of inactivity required to disconnect someone (Default: 100)
|
||||
checkTime - seconds between loops (Default: 100)
|
||||
: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
|
||||
@@ -170,8 +195,11 @@ class tokenList:
|
||||
|
||||
def spamProtectionResetLoop(self):
|
||||
"""
|
||||
Reset spam rate every 10 seconds.
|
||||
Start spam protection reset loop.
|
||||
Called every 10 seconds.
|
||||
CALL THIS FUNCTION ONLY ONCE!
|
||||
|
||||
:return:
|
||||
"""
|
||||
# Reset spamRate for every token
|
||||
for _, value in self.tokens.items():
|
||||
@@ -182,20 +210,26 @@ class tokenList:
|
||||
|
||||
def deleteBanchoSessions(self):
|
||||
"""
|
||||
Truncate bancho_sessions table.
|
||||
Remove all `peppy:sessions:*` redis keys.
|
||||
Call at bancho startup to delete old cached sessions
|
||||
|
||||
:return:
|
||||
"""
|
||||
glob.db.execute("TRUNCATE TABLE bancho_sessions")
|
||||
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 redis.RedisError:
|
||||
pass
|
||||
|
||||
|
||||
def tokenExists(self, username = "", userID = -1):
|
||||
"""
|
||||
Check if a token exists (aka check if someone is connected)
|
||||
|
||||
username -- Optional.
|
||||
userID -- Optional.
|
||||
return -- True if it exists, otherwise False
|
||||
|
||||
Check if a token exists
|
||||
Use username or userid, not both at the same time.
|
||||
|
||||
:param username: Optional.
|
||||
:param userID: Optional.
|
||||
:return: True if it exists, otherwise False
|
||||
"""
|
||||
if userID > -1:
|
||||
return True if self.getTokenFromUserID(userID) is not None else False
|
||||
|
83
pep.py
83
pep.py
@@ -1,21 +1,20 @@
|
||||
"""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
|
||||
import tornado.web
|
||||
from raven.contrib.tornado import AsyncSentryClient
|
||||
import redis
|
||||
|
||||
from common import generalUtils
|
||||
from common.constants import bcolors
|
||||
from common.db import dbConnector
|
||||
from common.ddog import datadogClient
|
||||
from common.log import logUtils as log
|
||||
from common.ripple import userUtils
|
||||
from common.redis import pubSub
|
||||
from common.web import schiavo
|
||||
from handlers import apiFokabotMessageHandler
|
||||
from handlers import apiIsOnlineHandler
|
||||
@@ -33,7 +32,13 @@ from objects import banchoConfig
|
||||
from objects import chatFilters
|
||||
from objects import fokabot
|
||||
from objects import glob
|
||||
from pubSubHandlers import changeUsernameHandler
|
||||
|
||||
from pubSubHandlers import disconnectHandler
|
||||
from pubSubHandlers import banHandler
|
||||
from pubSubHandlers import notificationHandler
|
||||
from pubSubHandlers import updateSilenceHandler
|
||||
from pubSubHandlers import updateStatsHandler
|
||||
|
||||
def make_app():
|
||||
return tornado.web.Application([
|
||||
@@ -92,6 +97,31 @@ if __name__ == "__main__":
|
||||
consoleHelper.printColored("[!] Error while connection to database. Please check your config.ini and run the server again", bcolors.RED)
|
||||
raise
|
||||
|
||||
# Connect to redis
|
||||
try:
|
||||
consoleHelper.printNoNl("> Connecting to redis... ")
|
||||
glob.redis = redis.Redis(glob.conf.config["redis"]["host"], glob.conf.config["redis"]["port"], glob.conf.config["redis"]["database"], glob.conf.config["redis"]["password"])
|
||||
glob.redis.ping()
|
||||
consoleHelper.printNoNl(" ")
|
||||
consoleHelper.printDone()
|
||||
except:
|
||||
# Exception while connecting to db
|
||||
consoleHelper.printError()
|
||||
consoleHelper.printColored("[!] Error while connection to redis. Please check your config.ini and run the server again", bcolors.RED)
|
||||
raise
|
||||
|
||||
# Empty redis cache
|
||||
try:
|
||||
# TODO: Make function or some redis meme
|
||||
glob.redis.set("ripple:online_users", 0)
|
||||
glob.redis.eval("return redis.call('del', unpack(redis.call('keys', ARGV[1])))", 0, "peppy:*")
|
||||
except redis.exceptions.ResponseError:
|
||||
# Script returns error if there are no keys starting with peppy:*
|
||||
pass
|
||||
|
||||
# Save peppy version in redis
|
||||
glob.redis.set("peppy:version", glob.VERSION)
|
||||
|
||||
# Load bancho_settings
|
||||
try:
|
||||
consoleHelper.printNoNl("> Loading bancho settings from DB... ")
|
||||
@@ -112,7 +142,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)
|
||||
|
||||
@@ -125,6 +155,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 FokaBot... ")
|
||||
fokabot.connect()
|
||||
consoleHelper.printDone()
|
||||
|
||||
# Initialize chat channels
|
||||
print("> Initializing chat channels... ")
|
||||
glob.channels.loadChannels()
|
||||
@@ -136,11 +171,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()
|
||||
@@ -151,11 +181,6 @@ if __name__ == "__main__":
|
||||
glob.tokens.spamProtectionResetLoop()
|
||||
consoleHelper.printDone()
|
||||
|
||||
# Cache user ids
|
||||
consoleHelper.printNoNl("> Caching user IDs... ")
|
||||
userUtils.cacheUserIDs()
|
||||
consoleHelper.printDone()
|
||||
|
||||
# Localize warning
|
||||
glob.localize = generalUtils.stringToBool(glob.conf.config["localize"]["enable"])
|
||||
if not glob.localize:
|
||||
@@ -163,7 +188,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)
|
||||
|
||||
@@ -202,6 +227,17 @@ if __name__ == "__main__":
|
||||
[
|
||||
datadogClient.periodicCheck("online_users", lambda: len(glob.tokens.tokens)),
|
||||
datadogClient.periodicCheck("multiplayer_matches", lambda: len(glob.matches.matches)),
|
||||
|
||||
#datadogClient.periodicCheck("ram_clients", lambda: generalUtils.getTotalSize(glob.tokens)),
|
||||
#datadogClient.periodicCheck("ram_matches", lambda: generalUtils.getTotalSize(glob.matches)),
|
||||
#datadogClient.periodicCheck("ram_channels", lambda: generalUtils.getTotalSize(glob.channels)),
|
||||
#datadogClient.periodicCheck("ram_file_buffers", lambda: generalUtils.getTotalSize(glob.fileBuffers)),
|
||||
#datadogClient.periodicCheck("ram_file_locks", lambda: generalUtils.getTotalSize(glob.fLocks)),
|
||||
#datadogClient.periodicCheck("ram_datadog", lambda: generalUtils.getTotalSize(glob.datadogClient)),
|
||||
#datadogClient.periodicCheck("ram_verified_cache", lambda: generalUtils.getTotalSize(glob.verifiedCache)),
|
||||
#datadogClient.periodicCheck("ram_irc", lambda: generalUtils.getTotalSize(glob.ircServer)),
|
||||
#datadogClient.periodicCheck("ram_tornado", lambda: generalUtils.getTotalSize(glob.application)),
|
||||
#datadogClient.periodicCheck("ram_db", lambda: generalUtils.getTotalSize(glob.db)),
|
||||
])
|
||||
else:
|
||||
consoleHelper.printColored("[!] Warning! Datadog stats tracking is disabled!", bcolors.YELLOW)
|
||||
@@ -212,9 +248,10 @@ 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)
|
||||
consoleHelper.printColored("> IRC server listening on 127.0.0.1:{}...".format(ircPort), bcolors.GREEN)
|
||||
@@ -223,15 +260,27 @@ if __name__ == "__main__":
|
||||
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)
|
||||
consoleHelper.printColored("> Tornado listening for HTTP(s) clients on 127.0.0.1:{}...".format(serverPort), bcolors.GREEN)
|
||||
|
||||
# Connect to pubsub channels
|
||||
pubSub.listener(glob.redis, {
|
||||
"peppy:disconnect": disconnectHandler.handler(),
|
||||
"peppy:change_username": changeUsernameHandler.handler(),
|
||||
"peppy:reload_settings": lambda x: x == b"reload" and glob.banchoConf.reload(),
|
||||
"peppy:update_cached_stats": updateStatsHandler.handler(),
|
||||
"peppy:silence": updateSilenceHandler.handler(),
|
||||
"peppy:ban": banHandler.handler(),
|
||||
"peppy:notification": notificationHandler.handler(),
|
||||
}).start()
|
||||
|
||||
# Start tornado
|
||||
glob.application.listen(serverPort)
|
||||
tornado.ioloop.IOLoop.instance().start()
|
||||
|
0
pubSubHandlers/__init__.py
Normal file
0
pubSubHandlers/__init__.py
Normal file
18
pubSubHandlers/banHandler.py
Normal file
18
pubSubHandlers/banHandler.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from common.redis import generalPubSubHandler
|
||||
from common.ripple import userUtils
|
||||
from objects import glob
|
||||
|
||||
class handler(generalPubSubHandler.generalPubSubHandler):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.type = "int"
|
||||
|
||||
def handle(self, userID):
|
||||
userID = super().parseData(userID)
|
||||
if userID is None:
|
||||
return
|
||||
targetToken = glob.tokens.getTokenFromUserID(userID)
|
||||
if targetToken is not None:
|
||||
targetToken.privileges = userUtils.getPrivileges(userID)
|
||||
targetToken.checkBanned()
|
||||
targetToken.checkRestricted()
|
50
pubSubHandlers/changeUsernameHandler.py
Normal file
50
pubSubHandlers/changeUsernameHandler.py
Normal file
@@ -0,0 +1,50 @@
|
||||
from common.redis import generalPubSubHandler
|
||||
from common.ripple import userUtils
|
||||
from common.log import logUtils as log
|
||||
from common.constants import actions
|
||||
from objects import glob
|
||||
|
||||
def handleUsernameChange(userID, newUsername, targetToken=None):
|
||||
try:
|
||||
userUtils.appendNotes(userID, "-- Username change: '{}' -> '{}'".format(userUtils.getUsername(userID), newUsername))
|
||||
userUtils.changeUsername(userID, newUsername=newUsername)
|
||||
if targetToken is not None:
|
||||
targetToken.kick("Your username has been changed to {}. Please log in again.".format(newUsername), "username_change")
|
||||
except userUtils.usernameAlreadyInUseError:
|
||||
log.rap(999, "Username change: {} is already in use!", through="Bancho")
|
||||
if targetToken is not None:
|
||||
targetToken.kick("There was a critical error while trying to change your username. Please contact a developer.", "username_change_fail")
|
||||
except userUtils.invalidUsernameError:
|
||||
log.rap(999, "Username change: {} is not a valid username!", through="Bancho")
|
||||
if targetToken is not None:
|
||||
targetToken.kick("There was a critical error while trying to change your username. Please contact a developer.", "username_change_fail")
|
||||
|
||||
class handler(generalPubSubHandler.generalPubSubHandler):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.structure = {
|
||||
"userID": 0,
|
||||
"newUsername": ""
|
||||
}
|
||||
|
||||
def handle(self, data):
|
||||
data = super().parseData(data)
|
||||
if data is None:
|
||||
return
|
||||
# Get the user's token
|
||||
targetToken = glob.tokens.getTokenFromUserID(data["userID"])
|
||||
if targetToken is None:
|
||||
# If the user is offline change username immediately
|
||||
handleUsernameChange(data["userID"], data["newUsername"])
|
||||
else:
|
||||
if targetToken.irc or (targetToken.actionID != actions.PLAYING and targetToken.actionID != actions.MULTIPLAYING):
|
||||
# If the user is online and he's connected through IRC or he's not playing,
|
||||
# change username and kick the user immediately
|
||||
handleUsernameChange(data["userID"], data["newUsername"], targetToken)
|
||||
else:
|
||||
# If the user is playing, delay the username change until he submits the score
|
||||
# On submit modular, lets will send the username change request again
|
||||
# through redis once the score has been submitted
|
||||
# The check is performed on bancho logout too, so if the user disconnects
|
||||
# without submitting a score, the username gets changed on bancho logout
|
||||
glob.redis.set("ripple:change_username_pending:{}".format(data["userID"]), data["newUsername"])
|
18
pubSubHandlers/disconnectHandler.py
Normal file
18
pubSubHandlers/disconnectHandler.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from common.redis import generalPubSubHandler
|
||||
from objects import glob
|
||||
|
||||
class handler(generalPubSubHandler.generalPubSubHandler):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.structure = {
|
||||
"userID": 0,
|
||||
"reason": ""
|
||||
}
|
||||
|
||||
def handle(self, data):
|
||||
data = super().parseData(data)
|
||||
if data is None:
|
||||
return
|
||||
targetToken = glob.tokens.getTokenFromUserID(data["userID"])
|
||||
if targetToken is not None:
|
||||
targetToken.kick(data["reason"], "pubsub_kick")
|
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"]))
|
15
pubSubHandlers/updateSilenceHandler.py
Normal file
15
pubSubHandlers/updateSilenceHandler.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from common.redis import generalPubSubHandler
|
||||
from objects import glob
|
||||
|
||||
class handler(generalPubSubHandler.generalPubSubHandler):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.type = "int"
|
||||
|
||||
def handle(self, userID):
|
||||
userID = super().parseData(userID)
|
||||
if userID is None:
|
||||
return
|
||||
targetToken = glob.tokens.getTokenFromUserID(userID)
|
||||
if targetToken is not None:
|
||||
targetToken.silence()
|
15
pubSubHandlers/updateStatsHandler.py
Normal file
15
pubSubHandlers/updateStatsHandler.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from common.redis import generalPubSubHandler
|
||||
from objects import glob
|
||||
|
||||
class handler(generalPubSubHandler.generalPubSubHandler):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.type = "int"
|
||||
|
||||
def handle(self, userID):
|
||||
userID = super().parseData(userID)
|
||||
if userID is None:
|
||||
return
|
||||
targetToken = glob.tokens.getTokenFromUserID(userID)
|
||||
if targetToken is not None:
|
||||
targetToken.updateCachedStats()
|
@@ -3,4 +3,8 @@ tornado
|
||||
mysqlclient
|
||||
psutil
|
||||
raven
|
||||
bcrypt
|
||||
bcrypt>=3.1.1
|
||||
dill
|
||||
redis
|
||||
cython
|
||||
datadog
|
17
setup.py
Normal file
17
setup.py
Normal file
@@ -0,0 +1,17 @@
|
||||
"""Cython build file"""
|
||||
from distutils.core import setup
|
||||
from distutils.extension import Extension
|
||||
from Cython.Build import cythonize
|
||||
import os
|
||||
|
||||
cythonExt = []
|
||||
for root, dirs, files in os.walk(os.getcwd()):
|
||||
for file in files:
|
||||
if file.endswith(".pyx"):
|
||||
filePath = os.path.relpath(os.path.join(root, file))
|
||||
cythonExt.append(Extension(filePath.replace("/", ".")[:-4], [filePath]))
|
||||
|
||||
setup(
|
||||
name = "pep.pyx modules",
|
||||
ext_modules = cythonize(cythonExt, nthreads = 4),
|
||||
)
|
Reference in New Issue
Block a user