2 Commits

Author SHA1 Message Date
Nyo
4cbaaafba5 .BANCHO. Better code for np bug fix 2016-10-31 11:57:11 +01:00
Nyo
fe283ff293 .BANCHO. .FIX. Fix np bug 2016-10-31 10:47:28 +01:00
72 changed files with 1089 additions and 1659 deletions

7
.gitignore vendored
View File

@@ -1,9 +1,8 @@
**/__pycache__
**/build
config.ini
filters.txt
.data
.idea
redistest.py
*.c
*.so
common_funzia
common_refractor
common_memato

View File

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

View File

@@ -1,8 +1,4 @@
## pep.py [![Code Health](https://landscape.io/github/osuripple/pep.py/master/landscape.svg?style=flat)](https://landscape.io/github/osuripple/pep.py/master)
- Origin: https://git.zxq.co/ripple/pep.py
- Mirror: https://github.com/osuripple/pep.py
## pep.py
This is Ripple's bancho server. It handles:
- Client login
- Online users listing and statuses
@@ -13,8 +9,6 @@ This is Ripple's bancho server. It handles:
## Requirements
- Python 3.5
- Cython
- C compiler
- MySQLdb (`mysqlclient`)
- Tornado
- Bcrypt
@@ -29,14 +23,9 @@ afterwards, install the required dependencies with pip
```
$ pip install -r requirements.txt
```
then, compile all `*.pyx` files to `*.so` or `*.dll` files using `setup.py` (distutils file)
```
$ python3 setup.py build_ext --inplace
```
finally, run pep.py once to create the default config file and edit it
then, 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

Submodule common updated: 6329b9ac2d...d68736652c

View File

@@ -1,3 +1,4 @@
""" Contains functions used to read specific client packets from byte stream """
from constants import dataTypes
from helpers import packetHelper
from constants import slotStatuses
@@ -99,7 +100,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

View File

@@ -1,3 +1,5 @@
"""Bancho exceptions"""
# TODO: Prints in exceptions
class loginFailedException(Exception):
pass
@@ -89,16 +91,4 @@ class unknownStreamException(Exception):
pass
class userTournamentException(Exception):
pass
class userAlreadyInChannelException(Exception):
pass
class userNotInChannelException(Exception):
pass
class missingReportInfoException(Exception):
pass
class invalidUserException(Exception):
pass

View File

@@ -1,11 +1,10 @@
import json
import random
import re
import requests
import time
from common import generalUtils
from common.constants import actions
from common.constants import mods
from common.log import logUtils as log
from common.ripple import userUtils
@@ -16,18 +15,17 @@ 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
: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
. . .
fro -- name of who triggered the command
chan -- channel where the message was sent
message -- list containing arguments passed from the message
[0] = first argument
[1] = second argument
. . .
return the message or **False** if there's no response by the bot
TODO: Change False to None, because False doesn't make any sense
@@ -38,7 +36,6 @@ 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":
@@ -122,18 +119,15 @@ def kickAll(fro, chan, message):
def kick(fro, chan, message):
# Get parameters
target = message[0].lower().replace("_", " ")
if target == "fokabot":
return "Nope."
target = message[0].replace("_", " ")
# Get target token and make sure is connected
tokens = glob.tokens.getTokenFromUsername(target, _all=True)
if len(tokens) == 0:
targetToken = glob.tokens.getTokenFromUsername(target)
if targetToken is None:
return "{} is not online".format(target)
# Kick users
for i in tokens:
i.kick()
# Kick user
targetToken.kick()
# Bot response
return "{} has been kicked from the server.".format(target)
@@ -167,11 +161,11 @@ def silence(fro, chan, message):
if unit == 's':
silenceTime = int(amount)
elif unit == 'm':
silenceTime = int(amount) * 60
silenceTime = int(amount)*60
elif unit == 'h':
silenceTime = int(amount) * 3600
silenceTime = int(amount)*3600
elif unit == 'd':
silenceTime = int(amount) * 86400
silenceTime = int(amount)*86400
else:
return "Invalid time unit (s/m/h/d)."
@@ -310,7 +304,22 @@ def systemShutdown(fro, chan, message):
return restartShutdown(False)
def systemReload(fro, chan, message):
glob.banchoConf.reload()
# 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))
return "Bancho settings reloaded!"
def systemMaintenance(fro, chan, message):
@@ -351,13 +360,7 @@ 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"
@@ -386,7 +389,7 @@ def getPPMessage(userID, just_data = False):
currentAcc = token.tillerino[2]
# Send request to LETS api
resp = requests.get("http://127.0.0.1:5002/api/v1/pp?b={}&m={}".format(currentMap, currentMods), timeout=10).text
resp = requests.get("http://127.0.0.1:5002/api/v1/pp?b={}&m={}".format(currentMap, currentMods, currentAcc), timeout=10).text
data = json.loads(resp)
# Make sure status is in response data
@@ -697,68 +700,41 @@ def updateBeatmap(fro, chan, message):
except:
return False
def report(fro, chan, message):
msg = ""
try:
# TODO: Rate limit
# Regex on message
reportRegex = re.compile("^(.+) \((.+)\)\:(?: )?(.+)?$")
result = reportRegex.search(" ".join(message))
def trick(fro, chan, message):
if chan.startswith("#"):
return False
token = glob.tokens.getTokenFromUsername(fro)
if token is None or token.zingheri != 0:
return False
token.zingheri = 1
return "As you want..."
# 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
def treat(fro, chan, message):
if chan.startswith("#"):
return False
token = glob.tokens.getTokenFromUsername(fro)
if token is None or token.zingheri != 0:
return False
if token.actionID != actions.IDLE and token.actionID != actions.AFK:
log.warning(str(token.actionID))
return "You must be in the main menu to give me a treat :3"
token.zingheri = -2
token.leaveStream("zingheri")
usePP = token.gameMode == gameModes.STD or token.gameMode == gameModes.MANIA
if usePP:
currentPP = userUtils.getPP(token.userID, token.gameMode)
gift = currentPP/3
userUtils.setPP(token.userID, token.gameMode, currentPP - gift)
userUtils.setPP(999, token.gameMode, userUtils.getPP(999, token.gameMode) + gift)
else:
currentScore = userUtils.getRankedScore(token.userID, token.gameMode)
gift = currentScore/3
userUtils.setRankedScore(token.userID, token.gameMode, currentScore - gift)
userUtils.setRankedScore(999, token.gameMode, userUtils.getRankedScore(999, token.gameMode) + gift)
token.updateCachedStats()
token.enqueue(serverPackets.userStats(token.userID))
token.enqueue(serverPackets.userStats(999, True))
return "You just gave me {num:.2f} {type} as a treat, thanks! ^.^".format(num=gift, type="pp" if usePP else "score")
"""
Commands list
@@ -768,6 +744,11 @@ 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 = [
{
@@ -779,7 +760,7 @@ commands = [
"callback": faq
}, {
"trigger": "!report",
"callback": report
"response": "Report command isn't here yet :c"
}, {
"trigger": "!help",
"response": "Click (here)[https://ripple.moe/index.php?p=16&id=4] for FokaBot's full command list"
@@ -807,7 +788,7 @@ commands = [
"callback": moderated
}, {
"trigger": "!kickall",
"privileges": privileges.ADMIN_MANAGE_SERVERS,
"privileges": privileges.ADMIN_KICK_USERS,
"callback": kickAll
}, {
"trigger": "!kick",
@@ -894,6 +875,12 @@ commands = [
}, {
"trigger": "!update",
"callback": updateBeatmap
}, {
"trigger": "trick",
"callback": trick
}, {
"trigger": "treat",
"callback": treat
}
#
# "trigger": "!acc",

View File

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

View File

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

View File

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

View File

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

View File

@@ -79,4 +79,6 @@ server_userPresenceBundle = 96
client_userPanelRequest = 97
client_tournamentMatchInfoRequest = 93
client_tournamentJoinMatchChannel = 108
client_tournamentLeaveMatchChannel = 109
client_tournamentLeaveMatchChannel = 109
server_zingheri = 105

View File

@@ -155,13 +155,11 @@ 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, [
[channel.name, dataTypes.STRING],
[chan, dataTypes.STRING],
[channel.description, dataTypes.STRING],
[len(glob.streams.streams["chat/{}".format(chan)].clients), dataTypes.UINT16]
[channel.getConnectedUsersCount(), dataTypes.UINT16]
])
def channelInfoEnd():
@@ -269,4 +267,7 @@ def notification(message):
return packetHelper.buildPacket(packetIDs.server_notification, [[message, dataTypes.STRING]])
def banchoRestart(msUntilReconnection):
return packetHelper.buildPacket(packetIDs.server_restart, [[msUntilReconnection, dataTypes.UINT32]])
return packetHelper.buildPacket(packetIDs.server_restart, [[msUntilReconnection, dataTypes.UINT32]])
def zingheri(message):
return packetHelper.buildPacket(packetIDs.server_zingheri, [[message, dataTypes.STRING]])

View File

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

View File

@@ -1,3 +1,4 @@
"""Bancho user ranks"""
NORMAL = 0
PLAYER = 1
BAT = 2

View File

@@ -1,7 +1,10 @@
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
import time
def handle(userToken, packetData):
# Get usertoken data
@@ -9,13 +12,14 @@ def handle(userToken, packetData):
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 userToken.restricted:
# userToken.checkRestricted(True)
if not userToken.restricted:
if userUtils.isRestricted(userID):
userToken.setRestricted()
# Change action packet
packetData = clientPackets.userActionChange(packetData)
@@ -31,10 +35,8 @@ if userToken.matchID != -1 and userToken.actionID != actions.MULTIPLAYING and us
'''
# Update cached stats if our pp changed if we've just submitted a score or we've changed gameMode
#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"]:
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
userToken.gameMode = packetData["gameMode"]
userToken.updateCachedStats()
@@ -44,6 +46,7 @@ if userToken.matchID != -1 and userToken.actionID != actions.MULTIPLAYING and us
userToken.actionMd5 = packetData["actionMd5"]
userToken.actionMods = packetData["actionMods"]
userToken.beatmapID = packetData["beatmapID"]
userToken.actionLatestUpdate = int(time.time())
# Enqueue our new user panel and stats to us and our spectators
recipients = [userToken]

View File

@@ -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.FREE_MOD:
if match.matchModMode == matchModModes.freeMod:
# Freemod
# Host can set global DT/HT
if userID == match.hostUserID:

View File

@@ -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.NOT_READY
if match.slots[i].status == slotStatuses.ready:
match.slots[i].status = slotStatuses.notReady
# 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.TEAM_VS or match.matchTeamType == matchTeamTypes.TAG_TEAM_VS:
if match.matchTeamType == matchTeamTypes.teamVs or match.matchTeamType == matchTeamTypes.tagTeamVs:
# Set teams
c=0
for i in range(0,16):
if match.slots[i].team == matchTeams.NO_TEAM:
match.slots[i].team = matchTeams.RED if c % 2 == 0 else matchTeams.BLUE
if match.slots[i].team == matchTeams.noTeam:
match.slots[i].team = matchTeams.red if c % 2 == 0 else matchTeams.blue
c+=1
else:
# Reset teams
for i in range(0,16):
match.slots[i].team = matchTeams.NO_TEAM
match.slots[i].team = matchTeams.noTeam
# Force no freemods if tag coop
if match.matchTeamType == matchTeamTypes.TAG_COOP or match.matchTeamType == matchTeamTypes.TAG_TEAM_VS:
match.matchModMode = matchModModes.NORMAL
if match.matchTeamType == matchTeamTypes.tagCoop or match.matchTeamType == matchTeamTypes.tagTeamVs:
match.matchModMode = matchModModes.normal
# Send updated settings
match.sendUpdates()

View File

@@ -1,6 +1,7 @@
from common.log import logUtils as log
from constants import clientPackets
from constants import exceptions
from constants import serverPackets
from objects import glob

View File

@@ -1,3 +1,4 @@
from common import generalUtils
from common.log import logUtils as log
from constants import clientPackets
from constants import exceptions

View File

@@ -15,7 +15,6 @@ from objects import glob
def handle(tornadoRequest):
# Data to return
responseToken = None
responseTokenString = "ayy"
responseData = bytes()
@@ -30,6 +29,9 @@ 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()
@@ -44,6 +46,7 @@ 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()
@@ -189,20 +192,19 @@ def handle(tornadoRequest):
# Get location and country from ip.zxq.co or database
if glob.localize:
# Get location and country from IP
latitude, longitude = locationHelper.getLocation(requestIP)
location = 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")
latitude = 0
longitude = 0
location = [0,0]
countryLetters = "XX"
country = countryHelper.getCountryID(userUtils.getCountry(userID))
# Set location and country
responseToken.setLocation(latitude, longitude)
responseToken.country = country
responseToken.setLocation(location)
responseToken.setCountry(country)
# Set country in db if user has no country (first bancho login)
if userUtils.getCountry(userID) == "XX":
@@ -218,23 +220,25 @@ 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 = responseToken.queue
responseData += serverPackets.notification("Our bancho server is in maintenance mode. Please try to login again later.")
responseData += serverPackets.loginFailed()
except exceptions.banchoRestartingException:
@@ -247,6 +251,7 @@ 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:
@@ -254,7 +259,10 @@ def handle(tornadoRequest):
finally:
# Console and discord log
if len(loginData) < 3:
log.info("Invalid bancho login request from **{}** (insufficient POST data)".format(requestIP), "bunker")
msg = "Invalid bancho login request from **{}** (insufficient POST data)".format(requestIP)
else:
msg = "Bancho login request from **{}** for user **{}** ({})".format(requestIP, loginData[0], "failed" if err == True else "success")
log.info(msg, "bunker")
# Return token string and data
return responseTokenString, responseData

View File

@@ -1,5 +1,4 @@
import time
import json
from common.log import logUtils as log
from constants import serverPackets
@@ -7,7 +6,7 @@ from helpers import chatHelper as chat
from objects import glob
def handle(userToken, _=None, deleteToken=True):
def handle(userToken, _=None):
# get usertoken data
userID = userToken.userID
username = userToken.username
@@ -17,7 +16,7 @@ def handle(userToken, _=None, deleteToken=True):
# the old logout packet will still be in the queue and will be sent to
# the server, so we accept logout packets sent at least 5 seconds after login
# if the user logs out before 5 seconds, he will be disconnected later with timeout check
if int(time.time() - userToken.loginTime) >= 5 or userToken.irc:
if (int(time.time()-userToken.loginTime) >= 5 or userToken.irc):
# Stop spectating
userToken.stopSpectating()
@@ -35,23 +34,11 @@ def handle(userToken, _=None, deleteToken=True):
glob.streams.broadcast("main", serverPackets.userLogout(userID))
# Disconnect from IRC if needed
if userToken.irc and glob.irc:
if userToken.irc == True and glob.irc == True:
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")
}))
glob.tokens.deleteToken(requestToken)
# Console output
log.info("{} has been disconnected. (logout)".format(username))

View File

@@ -1,6 +1,6 @@
from objects import glob
def handle(userToken, _, has):
def handle(userToken, packetData, has):
# Get usertoken data
userID = userToken.userID

View File

@@ -1,6 +1,6 @@
from objects import glob
def handle(userToken, _):
def handle(userToken, packetData):
# Get usertoken data
userID = userToken.userID

View File

@@ -1,4 +1,5 @@
from objects import glob
from constants import slotStatuses
from constants import serverPackets
def handle(userToken, packetData):

View File

@@ -1,6 +1,6 @@
from objects import glob
def handle(userToken, _):
def handle(userToken, packetData):
# Get userToken data
userID = userToken.userID

View File

@@ -1,6 +1,6 @@
from objects import glob
def handle(userToken, _):
def handle(userToken, packetData):
# Get userToken data
userID = userToken.userID

View File

@@ -1,4 +1,6 @@
from objects import glob
from constants import slotStatuses
from constants import serverPackets
def handle(userToken, _):

View File

@@ -1,15 +1,17 @@
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

View File

@@ -11,7 +11,7 @@ def handle(userToken, packetData):
packetData = clientPackets.setAwayMessage(packetData)
# Set token away message
userToken.awayMessage = packetData["awayMessage"]
userToken.setAwayMessage(packetData["awayMessage"])
# Send private message from fokabot
if packetData["awayMessage"] == "":

View File

@@ -1,5 +1,6 @@
from objects import glob
from constants import serverPackets
from constants import exceptions
def handle(userToken, packetData):
# get token data

View File

@@ -1,4 +1,5 @@
from constants import clientPackets
from constants import serverPackets
from objects import glob
def handle(userToken, packetData):

View File

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

View File

@@ -1,9 +1,6 @@
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
from helpers import chatHelper
@@ -11,9 +8,6 @@ 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"}
@@ -27,6 +21,7 @@ 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
@@ -40,5 +35,7 @@ class handler(requestsManager.asyncRequestHandler):
data["status"] = statusCode
# Send response
#self.clear()
self.write(json.dumps(data))
self.set_status(statusCode)
self.set_status(statusCode)
#self.finish(json.dumps(data))

View File

@@ -1,19 +1,11 @@
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"}
@@ -26,8 +18,7 @@ class handler(requestsManager.asyncRequestHandler):
username = None
userID = None
if "u" in self.request.arguments:
#username = self.get_argument("u").lower().replace(" ", "_")
username = userUtils.safeUsername(self.get_argument("u"))
username = self.get_argument("u")
else:
try:
userID = int(self.get_argument("id"))
@@ -38,7 +29,7 @@ class handler(requestsManager.asyncRequestHandler):
data["result"] = False
else:
if username is not None:
data["result"] = True if glob.tokens.getTokenFromUsername(username, safe=True) is not None else False
data["result"] = True if glob.tokens.getTokenFromUsername(username) is not None else False
else:
data["result"] = True if glob.tokens.getTokenFromUserID(userID) is not None else False
@@ -53,5 +44,7 @@ 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))

View File

@@ -1,23 +1,16 @@
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"] = int(glob.redis.get("ripple:online_users").decode("utf-8"))
data["result"] = len(glob.tokens.tokens)
# Status code and message
statusCode = 200
@@ -27,5 +20,7 @@ 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))

View File

@@ -1,17 +1,10 @@
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"}
@@ -27,5 +20,7 @@ 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))

View File

@@ -1,18 +1,11 @@
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"}

View File

@@ -1,9 +1,5 @@
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
@@ -12,9 +8,6 @@ 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"}

270
handlers/mainHandler.py Normal file
View File

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

View File

@@ -1,266 +0,0 @@
import datetime
import gzip
import sys
import traceback
import tornado.gen
import tornado.web
from raven.contrib.tornado import SentryMixin
from common.log import logUtils as log
from common.web import requestsManager
from constants import exceptions
from constants import packetIDs
from constants import serverPackets
from events import cantSpectateEvent
from events import changeActionEvent
from events import changeMatchModsEvent
from events import changeMatchPasswordEvent
from events import changeMatchSettingsEvent
from events import changeSlotEvent
from events import channelJoinEvent
from events import channelPartEvent
from events import createMatchEvent
from events import friendAddEvent
from events import friendRemoveEvent
from events import joinLobbyEvent
from events import joinMatchEvent
from events import loginEvent
from events import logoutEvent
from events import matchChangeTeamEvent
from events import matchCompleteEvent
from events import matchFailedEvent
from events import matchFramesEvent
from events import matchHasBeatmapEvent
from events import matchInviteEvent
from events import matchLockEvent
from events import matchNoBeatmapEvent
from events import matchPlayerLoadEvent
from events import matchReadyEvent
from events import matchSkipEvent
from events import matchStartEvent
from events import matchTransferHostEvent
from events import partLobbyEvent
from events import partMatchEvent
from events import requestStatusUpdateEvent
from events import sendPrivateMessageEvent
from events import sendPublicMessageEvent
from events import setAwayMessageEvent
from events import spectateFramesEvent
from events import startSpectatingEvent
from events import stopSpectatingEvent
from events import userPanelRequestEvent
from events import userStatsRequestEvent
from events import tournamentMatchInfoRequestEvent
from events import tournamentJoinMatchChannelEvent
from events import tournamentLeaveMatchChannelEvent
from helpers import packetHelper
from objects import glob
from common.sentry import sentry
class handler(requestsManager.asyncRequestHandler):
@tornado.web.asynchronous
@tornado.gen.engine
@sentry.captureTornado
def asyncPost(self):
# Track time if needed
if glob.outputRequestTime:
# Start time
st = datetime.datetime.now()
# Client's token string and request data
requestTokenString = self.request.headers.get("osu-token")
requestData = self.request.body
# Server's token string and request data
responseTokenString = "ayy"
responseData = bytes()
if requestTokenString is None:
# No token, first request. Handle login.
responseTokenString, responseData = loginEvent.handle(self)
else:
userToken = None # default value
try:
# This is not the first packet, send response based on client's request
# Packet start position, used to read stacked packets
pos = 0
# Make sure the token exists
if requestTokenString not in glob.tokens.tokens:
raise exceptions.tokenNotFoundException()
# Token exists, get its object and lock it
userToken = glob.tokens.tokens[requestTokenString]
userToken.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>&copy; Ripple team, 2016</i></pre></body></html>"
self.write(html)

View File

@@ -12,11 +12,14 @@ def joinChannel(userID = 0, channel = "", token = None, toIRC = True):
"""
Join a channel
: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
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
"""
try:
# Get token if not defined
@@ -27,32 +30,44 @@ def joinChannel(userID = 0, channel = "", token = None, toIRC = True):
raise exceptions.userNotFoundException
else:
token = token
userID = token.userID
# Get usertoken data
username = token.username
# Normal channel, do check stuff
# Make sure the channel exists
if channel not in glob.channels.channels:
raise exceptions.channelUnknownException()
raise exceptions.channelUnknownException
# Check channel permissions
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)
# Add the channel to our joined channel
token.joinChannel(glob.channels.channels[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))
# Send channel joined (IRC)
if glob.irc == True and toIRC == True:
glob.ircServer.banchoJoinChannel(token.username, channel)
glob.ircServer.banchoJoinChannel(username, channel)
# Console output
log.info("{} joined channel {}".format(token.username, channel))
log.info("{} joined channel {}".format(username, channel))
# IRC code return
return 0
except exceptions.channelNoPermissionsException:
log.warning("{} attempted to join channel {}, but they have no read permissions".format(token.username, channel))
log.warning("{} attempted to join channel {}, but they have no read permissions".format(username, channel))
return 403
except exceptions.channelUnknownException:
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))
log.warning("{} attempted to join an unknown channel ({})".format(username, channel))
return 403
except exceptions.userNotFoundException:
log.warning("User not connected to IRC/Bancho")
@@ -62,18 +77,17 @@ def partChannel(userID = 0, channel = "", token = None, toIRC = True, kick = Fal
"""
Part a channel
: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
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
"""
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)
@@ -82,6 +96,10 @@ 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)
@@ -101,20 +119,12 @@ 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()
# Make sure the user is in the channel
if channel not in token.joinedChannels:
raise exceptions.userNotInChannelException()
raise exceptions.channelUnknownException
# Part channel (token-side and channel-side)
channelObject = glob.channels.channels[channel]
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)
token.partChannel(channel)
channelObject.userPart(userID)
# Force close tab if needed
# NOTE: Maybe always needed, will check later
@@ -123,19 +133,16 @@ def partChannel(userID = 0, channel = "", token = None, toIRC = True, kick = Fal
# IRC part
if glob.irc == True and toIRC == True:
glob.ircServer.banchoPartChannel(token.username, channel)
glob.ircServer.banchoPartChannel(username, channel)
# Console output
log.info("{} parted channel {} ({})".format(token.username, channel, channelClient))
log.info("{} parted channel {} ({})".format(username, channel, channelClient))
# Return IRC code
return 0
except exceptions.channelUnknownException:
log.warning("{} attempted to part an unknown channel ({})".format(token.username, channel))
log.warning("{} attempted to part an unknown channel ({})".format(username, channel))
return 403
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
@@ -144,15 +151,18 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
"""
Send a message to osu!bancho and IRC server
: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
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
"""
try:
#tokenString = ""
tokenString = ""
# Get token object if not passed
if token is None:
token = glob.tokens.getTokenFromUsername(fro)
@@ -161,7 +171,11 @@ 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
tokenString = token.token
# Set some variables
userID = token.userID
username = token.username
# Make sure this is not a tournament client
if token.tournament:
@@ -175,16 +189,12 @@ 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 = token.userID
s = userID
else:
s = token.spectatingUserID
to = "#spect_{}".format(s)
@@ -194,6 +204,7 @@ 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
@@ -201,7 +212,7 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
message = glob.chatFilters.filterMessage(message)
# Build packet bytes
packet = serverPackets.sendMessage(token.username, toClient, message)
packet = serverPackets.sendMessage(username, toClient, message)
# Send the message
isChannel = to.startswith("#")
@@ -209,31 +220,35 @@ 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()
# Add message in buffer
token.addMessageInBuffer(to, message)
raise exceptions.channelNoPermissionsException
# Everything seems fine, build recipients list and send packet
glob.streams.broadcast("chat/{}".format(to), packet, but=[token.token])
recipients = glob.channels.channels[to].getConnectedUsers()[:]
for key, value in glob.tokens.tokens.items():
# Skip our client and irc clients
if key == tokenString or value.irc == True:
continue
# Send to this client if it's connected to the channel
if value.userID in recipients:
value.enqueue(packet)
else:
# 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":
@@ -241,10 +256,6 @@ 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])
@@ -257,38 +268,38 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
glob.ircServer.banchoMessage(fro, to, message)
# Spam protection (ignore FokaBot)
if token.userID > 999:
if userID > 999:
token.spamProtection()
# Fokabot message
if isChannel == True or to.lower() == "fokabot":
fokaMessage = fokabot.fokabotResponse(token.username, to, message)
fokaMessage = fokabot.fokabotResponse(username, to, message)
if fokaMessage:
sendMessage("FokaBot", to if isChannel else fro, fokaMessage)
# File and discord logs (public chat only)
if to.startswith("#") and not (message.startswith("\x01ACTION is playing") and to.startswith("#spect_")):
log.chat("{fro} @ {to}: {message}".format(fro=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]))
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]))
return 0
except exceptions.userSilencedException:
token.enqueue(serverPackets.silenceEndTime(token.getSilenceSecondsLeft()))
log.warning("{} tried to send a message during silence".format(token.username))
log.warning("{} tried to send a message during silence".format(username))
return 404
except exceptions.channelModeratedException:
log.warning("{} tried to send a message to a channel that is in moderated mode ({})".format(token.username, to))
log.warning("{} tried to send a message to a channel that is in moderated mode ({})".format(username, to))
return 404
except exceptions.channelUnknownException:
log.warning("{} tried to send a message to an unknown channel ({})".format(token.username, to))
log.warning("{} tried to send a message to an unknown channel ({})".format(username, to))
return 403
except exceptions.channelNoPermissionsException:
log.warning("{} tried to send a message to channel {}, but they have no write permissions".format(token.username, to))
log.warning("{} tried to send a message to channel {}, but they have no write permissions".format(username, to))
return 404
except exceptions.userRestrictedException:
log.warning("{} tried to send a message {}, but the recipient is in restricted mode".format(token.username, to))
log.warning("{} tried to send a message {}, but the recipient is in restricted mode".format(username, to))
return 404
except exceptions.userTournamentException:
log.warning("{} tried to send a message {}, but the recipient is a tournament client".format(token.username, to))
log.warning("{} tried to send a message {}, but the recipient is a tournament client".format(username, to))
return 404
except exceptions.userNotFoundException:
log.warning("User not connected to IRC/Bancho")
@@ -297,12 +308,6 @@ 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:
@@ -317,22 +322,9 @@ 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))
@@ -343,13 +335,6 @@ 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))
@@ -358,13 +343,6 @@ 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))
@@ -375,30 +353,8 @@ 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
return partChannel(userID, channel)

View File

@@ -5,9 +5,9 @@ class config:
# Check if config.ini exists and load/generate it
def __init__(self, file):
"""
Initialize a config file object
Initialize a config object
:param file: file name
file -- filename
"""
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 is the config file has all required keys
Check if this config has the required keys
:return: True if valid, False if not valid
return -- True if valid, False if not
"""
try:
# Try to get all the required keys
@@ -37,11 +37,6 @@ 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")
@@ -74,14 +69,12 @@ class config:
self.config.get("localize","enable")
self.config.get("localize","ipapiurl")
return True
except configparser.Error:
except:
return False
def generateDefaultConfig(self):
"""
Write a default config file to disk
:return:
Open and set default keys for that config file
"""
# Open config.ini in write mode
f = open(self.fileName, "w")
@@ -94,12 +87,6 @@ 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")
@@ -127,9 +114,9 @@ class config:
self.config.set("discord", "devgroup", "")
self.config.add_section("datadog")
self.config.set("datadog", "enable", "0")
self.config.set("datadog", "apikey", "")
self.config.set("datadog", "appkey", "")
self.config.set("datadog", "enable")
self.config.set("datadog", "apikey")
self.config.set("datadog", "appkey")
self.config.add_section("irc")
self.config.set("irc", "enable", "1")

View File

@@ -1,12 +1,11 @@
from common.constants import bcolors
from objects import glob
def printServerStartHeader(asciiArt=True):
def printServerStartHeader(asciiArt):
"""
Print server start message
Print server start header with optional ascii art
:param asciiArt: print BanchoBoat ascii art. Default: True
:return:
asciiArt -- if True, will print ascii art too
"""
if asciiArt:
print("{} _ __".format(bcolors.GREEN))
@@ -33,43 +32,35 @@ def printServerStartHeader(asciiArt=True):
def printNoNl(string):
"""
Print a string without \n at the end
Print string without new line at the end
:param string: string to print
:return:
string -- string to print
"""
print(string, end="")
def printColored(string, color):
"""
Print a colored string
Print colored string
:param string: string to print
:param color: ANSI color code
:return:
string -- string to print
color -- see bcolors.py
"""
print("{}{}{}".format(color, string, bcolors.ENDC))
def printError():
"""
Print a red "Error"
:return:
Print error text FOR LOADING
"""
printColored("Error", bcolors.RED)
def printDone():
"""
Print a green "Done"
:return:
Print error text FOR LOADING
"""
printColored("Done", bcolors.GREEN)
def printWarning():
"""
Print a yellow "Warning"
:return:
Print error text FOR LOADING
"""
printColored("Warning", bcolors.YELLOW)

View File

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

View File

@@ -7,10 +7,10 @@ from objects import glob
def getCountry(ip):
"""
Get country from IP address using geoip api
Get country from IP address
:param ip: IP address
:return: country code. XX if invalid.
ip -- IP Address
return -- Country code (2 letters)
"""
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 using geoip api
Get latitude and longitude from IP address
:param ip: IP address
:return: (latitude, longitude)
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]

View File

@@ -1,15 +1,15 @@
import struct
from constants import dataTypes
cpdef bytearray uleb128Encode(int num):
def uleb128Encode(num):
"""
Encode an int to uleb128
Encode int -> uleb128
:param num: int to encode
:return: bytearray with encoded number
num -- int to encode
return -- bytearray with encoded number
"""
cdef bytearray arr = bytearray()
cdef int length = 0
arr = bytearray()
length = 0
if num == 0:
return bytearray(b"\x00")
@@ -23,16 +23,15 @@ cpdef bytearray uleb128Encode(int num):
return arr
cpdef list uleb128Decode(bytes num):
def uleb128Decode(num):
"""
Decode a uleb128 to int
Decode uleb128 -> int
:param num: encoded uleb128 int
:return: (total, length)
num -- encoded uleb128
return -- list. [total, length]
"""
cdef int shift = 0
cdef list arr = [0,0] #total, length
cdef int b
shift = 0
arr = [0,0] #total, length
while True:
b = num[arr[1]]
@@ -44,14 +43,16 @@ cpdef list uleb128Decode(bytes num):
return arr
cpdef unpackData(bytes data, int dataType):
def unpackData(data, dataType):
"""
Unpacks a single section of a packet.
Unpacks data according to dataType
:param data: bytes to unpack
:param dataType: data type
:return: unpacked bytes
data -- bytes array to unpack
dataType -- data type. See dataTypes.py
return -- unpacked bytes
"""
# Get right pack Type
if dataType == dataTypes.UINT16:
unpackType = "<H"
@@ -75,17 +76,18 @@ cpdef unpackData(bytes data, int dataType):
# Unpack
return struct.unpack(unpackType, bytes(data))[0]
cpdef bytes packData(__data, int dataType):
def packData(__data, dataType):
"""
Packs a single section of a packet.
Packs data according to dataType
:param __data: data to pack
:param dataType: data type
:return: packed bytes
data -- bytes to pack
dataType -- data type. See dataTypes.py
return -- packed bytes
"""
cdef bytes data = bytes() # data to return
cdef bint pack = True # if True, use pack. False only with strings
cdef str packType
data = bytes() # data to return
pack = True # if True, use pack. False only with strings
# Get right pack Type
if dataType == dataTypes.BBYTES:
@@ -136,24 +138,23 @@ cpdef bytes packData(__data, int dataType):
return data
cpdef bytes buildPacket(int __packet, list __packetData = None):
def buildPacket(__packet, __packetData=None):
"""
Builds a packet
Build a packet
:param __packet: packet ID
:param __packetData: packet structure [[data, dataType], [data, dataType], ...]
:return: packet bytes
packet -- packet id (int)
packetData -- list [[data, dataType], [data, dataType], ...]
return -- packet bytes
"""
# Default argument
# Set some variables
if __packetData is None:
__packetData = []
# Set some variables
cdef bytes packetData = bytes()
cdef int packetLength = 0
cdef bytes packetBytes = bytes()
packetData = bytes()
packetLength = 0
packetBytes = bytes()
# Pack packet data
cdef list i
for i in __packetData:
packetData += packData(i[0], i[1])
@@ -167,43 +168,42 @@ cpdef bytes buildPacket(int __packet, list __packetData = None):
packetBytes += packetData # packet data
return packetBytes
cpdef int readPacketID(bytes stream):
def readPacketID(stream):
"""
Read packetID (first two bytes) from a packet
Read packetID from stream (0-1 bytes)
:param stream: packet bytes
:return: packet ID
stream -- data stream
return -- packet ID (int)
"""
return unpackData(stream[0:2], dataTypes.UINT16)
cpdef int readPacketLength(bytes stream):
def readPacketLength(stream):
"""
Read packet data length (3:7 bytes) from a packet
Read packet length from stream (3-4-5-6 bytes)
:param stream: packet bytes
:return: packet data length
stream -- data stream
return -- packet length (int)
"""
return unpackData(stream[3:7], dataTypes.UINT32)
cpdef readPacketData(bytes stream, list structure=None, bint hasFirstBytes = True):
def readPacketData(stream, structure=None, hasFirstBytes = True):
"""
Read packet data from `stream` according to `structure`
: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 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
"""
# Default list argument
# Read packet ID (first 2 bytes)
if structure is None:
structure = []
# Read packet ID (first 2 bytes)
cdef dict data = {}
data = {}
# Skip packet ID and packet length if needed
cdef start, end
if hasFirstBytes:
end = 7
start = 7
@@ -212,8 +212,6 @@ cpdef readPacketData(bytes stream, list structure=None, bint hasFirstBytes = Tru
start = 0
# Read packet
cdef list i
cdef bint unpack
for i in structure:
start = end
unpack = True
@@ -248,10 +246,7 @@ cpdef readPacketData(bytes stream, list structure=None, bint hasFirstBytes = Tru
end = start+length[0]+length[1]+1
# Read bytes
#data[i[0]] = ''.join(chr(j) for j in stream[start+1+length[1]:end])
data[i[0]] = ""
for j in stream[start+1+length[1]:end]:
data[i[0]] += chr(j)
data[i[0]] = ''.join(chr(j) for j in stream[start+1+length[1]:end])
elif i[1] == dataTypes.BYTE:
end = start+1
elif i[1] == dataTypes.UINT16 or i[1] == dataTypes.SINT16:

View File

@@ -17,7 +17,6 @@ from objects import glob
def dispose():
"""
Perform some clean up. Called on shutdown.
:return:
"""
print("> Disposing server... ")
@@ -28,7 +27,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
@@ -36,14 +35,12 @@ def scheduleShutdown(sendRestartTime, restart, message = "", delay=20):
"""
Schedule a server 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:
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
"""
# Console output
log.info("Pep.py will {} in {} seconds!".format("restart" if restart else "shutdown", sendRestartTime+delay), "bunker")
log.info("Pep.py will {} in {} seconds!".format("restart" if restart else "shutdown", sendRestartTime+delay))
log.info("Sending server restart packets in {} seconds...".format(sendRestartTime))
# Send notification if set
@@ -64,21 +61,13 @@ def scheduleShutdown(sendRestartTime, restart, message = "", delay=20):
threading.Timer(sendRestartTime+delay, action).start()
def restartServer():
"""
Restart pep.py
:return:
"""
"""Restart pep.py script"""
log.info("Restarting pep.py...")
dispose()
os.execv(sys.executable, [sys.executable] + sys.argv)
def shutdownServer():
"""
Shutdown pep.py
:return:
"""
"""Shutdown pep.py"""
log.info("Shutting down pep.py...")
dispose()
sig = signal.SIGKILL if runningUnderUnix() else signal.CTRL_C_EVENT
@@ -88,7 +77,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)}

View File

@@ -22,15 +22,18 @@ 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
:param server: server object
:param sock: socket connection object
:return:
server -- server object
sock -- socket connection object
"""
self.__timestamp = time.time()
self.__readbuffer = ""
@@ -57,8 +60,7 @@ class Client:
Add a message (basic string) to client buffer.
This is the lowest possible level.
:param msg: message to add
:return:
msg -- message to add
"""
self.__writebuffer += msg + "\r\n"
@@ -67,7 +69,7 @@ class Client:
"""
Return this client's write buffer size
:return: write buffer size
return -- write buffer size
"""
return len(self.__writebuffer)
@@ -76,8 +78,7 @@ class Client:
"""
Add an IRC-like message to client buffer.
:param msg: message (without IRC stuff)
:return:
msg -- message (without IRC stuff)
"""
self.message(":{} {}".format(self.server.host, msg))
@@ -86,11 +87,10 @@ class Client:
"""
Add an IRC-like message to client buffer with code
:param code: response code
:param message: response message
:param nickname: receiver nickname
:param channel: optional
:return:
code -- response code
message -- response message
nickname -- receiver nickname
channel -- optional
"""
if nickname == "":
nickname = self.IRCUsername
@@ -103,8 +103,7 @@ class Client:
"""
Add a 403 reply (no such channel) to client buffer.
:param channel:
:return:
channel -- meh
"""
self.replyCode(403, "{} :No such channel".format(channel))
@@ -113,8 +112,7 @@ class Client:
"""
Add a 461 reply (not enough parameters) to client buffer
:param command: name of the command that had not enough parameters
:return:
command -- command that had not enough parameters
"""
self.replyCode(403, "{} :Not enough parameters".format(command))
@@ -123,9 +121,8 @@ class Client:
"""
Disconnects this client from the IRC server
:param quitmsg: IRC quit message. Default: 'Client quit'
:param callLogout: if True, call logoutEvent on bancho
:return:
quitmsg -- IRC quit message. Default: 'Client quit'
callLogout -- if True, call logoutEvent on bancho
"""
# Send error to client and close socket
self.message("ERROR :{}".format(quitmsg))
@@ -136,16 +133,12 @@ class Client:
self.server.removeClient(self, quitmsg)
# Bancho logout
if callLogout and self.banchoUsername != "":
if callLogout:
chat.IRCDisconnect(self.IRCUsername)
def readSocket(self):
"""
Read data coming from this client socket
:return:
"""
"""Read data coming from this client socket"""
try:
# Try to read incoming data from socket
data = self.socket.recv(2 ** 10)
@@ -168,11 +161,7 @@ class Client:
def parseBuffer(self):
"""
Parse self.__readbuffer, get command, arguments and call its handler
:return:
"""
"""Parse self.__readbuffer, get command, arguments and call its handler"""
# Get lines from buffer
lines = self.__linesep_regexp.split(self.__readbuffer)
self.__readbuffer = lines[-1]
@@ -209,11 +198,7 @@ class Client:
def writeSocket(self):
"""
Write buffer to socket
:return:
"""
"""Write buffer to socket"""
try:
sent = self.socket.send(self.__writebuffer.encode())
log.debug("[IRC] [{}:{}] <- {}".format(self.ip, self.port, self.__writebuffer[:sent]))
@@ -221,13 +206,9 @@ class Client:
except socket.error as x:
self.disconnect(str(x))
def checkAlive(self):
"""
Check if this client is still connected.
If the client is dead, disconnect it.
:return:
"""
def checkAlive(self):
"""Check if this client is still connected"""
now = time.time()
if self.__timestamp + 180 < now:
self.disconnect("ping timeout")
@@ -243,19 +224,11 @@ class Client:
def sendLusers(self):
"""
Send lusers response to this client
:return:
"""
"""Send lusers response to this client"""
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
:return:
"""
"""Send MOTD to this client"""
self.replyCode(375, "- {} Message of the day - ".format(self.server.host))
if len(self.server.motd) == 0:
self.replyCode(422, "MOTD File is missing")
@@ -323,6 +296,7 @@ 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":
@@ -350,11 +324,11 @@ class Client:
self.sendMotd()
self.__handleCommand = self.mainHandler
def quitHandler(self, _, arguments):
def quitHandler(self, command, arguments):
"""QUIT command handler"""
self.disconnect(self.IRCUsername if len(arguments) < 1 else arguments[0])
def joinHandler(self, _, arguments):
def joinHandler(self, command, arguments):
"""JOIN command handler"""
if len(arguments) < 1:
self.reply461("JOIN")
@@ -367,13 +341,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(",")
@@ -402,15 +376,13 @@ class Client:
self.replyCode(332, description, channel=channel)
# Build connected users list
if "chat/{}".format(channel) not in glob.streams.streams:
self.reply403(channel)
continue
users = glob.streams.streams["chat/{}".format(channel)].clients
users = glob.channels.channels[channel].getConnectedUsers()[:]
usernames = []
for user in users:
if user not in glob.tokens.tokens:
token = glob.tokens.getTokenFromUserID(user)
if token is None:
continue
usernames.append(chat.fixUsernameForIRC(glob.tokens.tokens[user].username))
usernames.append(chat.fixUsernameForIRC(token.username))
usernames = " ".join(usernames)
# Send IRC users list
@@ -421,7 +393,7 @@ class Client:
self.reply403(channel)
continue
def partHandler(self, _, arguments):
def partHandler(self, command, arguments):
"""PART command handler"""
if len(arguments) < 1:
self.reply461("PART")
@@ -468,6 +440,7 @@ class Client:
# Send the message to bancho and reply
if not recipientIRC.startswith("#"):
print("PMPMPM!!!!!!!!!!")
recipientBancho = chat.fixUsernameForBancho(recipientIRC)
else:
recipientBancho = recipientIRC
@@ -505,7 +478,7 @@ class Client:
"""LUSERS command handler"""
self.sendLusers()
def pingHandler(self, _, arguments):
def pingHandler(self, command, arguments):
"""PING command handler"""
if len(arguments) < 1:
self.replyCode(409, "No origin specified")
@@ -516,17 +489,10 @@ 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": self.awayHandler,
#"AWAY": away_handler,
#"ISON": ison_handler,
"JOIN": self.joinHandler,
#"LIST": list_handler,
@@ -552,20 +518,24 @@ 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.
self.clients = {} # Socket --> Client instance.
self.motd = ["Welcome to pep.py's embedded IRC server!", "This is a VERY simple IRC server and it's still in beta.", "Expect things to crash and not work as expected :("]
def forceDisconnection(self, username, isBanchoUsername=True):
"""
Disconnect someone from IRC if connected
:param username: victim
:param isBanchoUsername: if True, username is a bancho username, else convert it to a bancho username
:return:
username -- victim
"""
for _, value in self.clients.items():
if (value.IRCUsername == username and not isBanchoUsername) or (value.banchoUsername == username and isBanchoUsername):
@@ -576,9 +546,8 @@ class Server:
"""
Let every IRC client connected to a specific client know that 'username' joined the channel from bancho
:param username: username of bancho user
:param channel: joined channel name
:return:
username -- username of bancho user
channel -- joined channel name
"""
username = chat.fixUsernameForIRC(username)
for _, value in self.clients.items():
@@ -589,9 +558,8 @@ class Server:
"""
Let every IRC client connected to a specific client know that 'username' parted the channel from bancho
:param username: username of bancho user
:param channel: joined channel name
:return:
username -- username of bancho user
channel -- joined channel name
"""
username = chat.fixUsernameForIRC(username)
for _, value in self.clients.items():
@@ -602,10 +570,9 @@ class Server:
"""
Send a message to IRC when someone sends it from bancho
:param fro: sender username
:param to: receiver username
:param message: text of the message
:return:
fro -- sender username
to -- receiver username
message -- text of the message
"""
fro = chat.fixUsernameForIRC(fro)
to = chat.fixUsernameForIRC(to)
@@ -621,24 +588,19 @@ class Server:
value.message(":{} PRIVMSG {} :{}".format(fro, to, message))
def removeClient(self, client, _):
def removeClient(self, client, quitmsg):
"""
Remove a client from connected clients
:param client: client object
:return:
client -- client object
quitmsg -- QUIT argument, useless atm
"""
if client.socket in self.clients:
del self.clients[client.socket]
def start(self):
"""
Start IRC server main loop
:return:
"""
"""Start IRC server main loop"""
# Sentry
sentryClient = None
if glob.sentry:
sentryClient = raven.Client(glob.conf.config["sentry"]["ircdns"])
@@ -660,7 +622,7 @@ class Server:
[x.socket for x in self.clients.values()
if x.writeBufferSize() > 0],
[],
1)
2)
# Handle incoming connections
for x in iwtd:
@@ -671,7 +633,7 @@ class Server:
try:
self.clients[conn] = Client(self, conn)
log.info("[IRC] Accepted connection from {}:{}".format(addr[0], addr[1]))
except socket.error:
except socket.error as e:
try:
conn.close()
except:
@@ -690,15 +652,9 @@ class Server:
lastAliveCheck = now
except:
log.error("[IRC] Unknown error!\n```\n{}\n{}```".format(sys.exc_info(), traceback.format_exc()))
if glob.sentry and sentryClient is not None:
if glob.sentry:
sentryClient.captureException()
def main(port=6667):
"""
Create and start an IRC server
:param port: IRC port. Default: 6667
:return:
"""
glob.ircServer = Server(port)
glob.ircServer.start()

View File

@@ -1,6 +1,5 @@
# TODO: Rewrite this shit
from common import generalUtils
from constants import serverPackets
from objects import glob
@@ -42,20 +41,3 @@ 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))

View File

@@ -1,16 +1,20 @@
from objects import glob
class channel:
"""
A chat channel
"""
def __init__(self, name, description, publicRead, publicWrite, temp, hidden):
"""
Create a new chat channel object
: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
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
"""
self.name = name
self.description = description
@@ -18,6 +22,7 @@ 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)
@@ -27,7 +32,41 @@ class channel:
elif self.name.startswith("#multi_"):
self.clientName = "#multiplayer"
# Make Foka join the channel
fokaToken = glob.tokens.getTokenFromUserID(999)
if fokaToken is not None:
fokaToken.joinChannel(self)
def userJoin(self, userID):
"""
Add a user to connected users
userID -- user ID that joined the channel
"""
if userID not in self.connectedUsers:
self.connectedUsers.append(userID)
def userPart(self, userID):
"""
Remove a user from connected users
userID -- user ID that left the channel
"""
if userID in self.connectedUsers:
self.connectedUsers.remove(userID)
# Remove temp channels if empty or there's only fokabot connected
l = len(self.connectedUsers)
if self.temp == True and ((l == 0) or (l == 1 and 999 in self.connectedUsers)):
glob.channels.removeChannel(self.name)
def getConnectedUsers(self):
"""
Get connected user IDs list
return -- connectedUsers list
"""
return self.connectedUsers
def getConnectedUsersCount(self):
"""
Count connected users
return -- connected users number
"""
return len(self.connectedUsers)

View File

@@ -1,18 +1,21 @@
from common.log import logUtils as log
from objects import channel
from objects import glob
from helpers import chatHelper as chat
class channelList:
def __init__(self):
self.channels = {}
"""
Channel list
channels -- dictionary. key: channel name, value: channel object
"""
channels = {}
def loadChannels(self):
"""
Load chat channels from db and add them to channels list
:return:
Load chat channels from db and add them to channels dictionary
"""
# Get channels from DB
channels = glob.db.fetchAll("SELECT * FROM bancho_channels")
@@ -25,17 +28,15 @@ class channelList:
def addChannel(self, name, description, publicRead, publicWrite, temp = False, hidden = False):
"""
Add a channel to channels list
Add a channel object to channels dictionary
: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:
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.
"""
glob.streams.add("chat/{}".format(name))
self.channels[name] = channel.channel(name, description, publicRead, publicWrite, temp, hidden)
log.info("Created channel {}".format(name))
@@ -44,12 +45,11 @@ 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
:param name: channel name
:return: True if the channel was created, otherwise False
name -- channel name
return -- True if channel was created, False if failed
"""
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,19 +57,10 @@ class channelList:
"""
Removes a channel from channels list
:param name: channel name
:return:
name -- channel name
"""
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))

View File

@@ -1,20 +1,9 @@
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 = {}
@@ -30,12 +19,6 @@ 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(" ")

View File

@@ -1,47 +1,48 @@
"""FokaBot related functions"""
import random
import re
import time
from common import generalUtils
from common.constants import actions
from common.ripple import userUtils
from constants import fokabotCommands
from constants import serverPackets
from objects import glob
from common.log import logUtils as log
import threading
from helpers import chatHelper as chat
from constants import serverPackets
# Tillerino np regex, compiled only once to increase performance
npRegex = re.compile("^https?:\\/\\/osu\\.ppy\\.sh\\/b\\/(\\d*)")
def connect():
"""
Connect FokaBot to Bancho
:return:
"""
"""Add FokaBot to connected users and send userpanel/stats packet to everyone"""
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():
"""
Disconnect FokaBot from Bancho
:return:
"""
"""Remove FokaBot from connected users"""
glob.tokens.deleteToken(glob.tokens.getTokenFromUserID(999))
def fokabotResponse(fro, chan, message):
"""
Check if a message has triggered FokaBot
Check if a message has triggered fokabot (and return its response)
:param fro: sender username
:param chan: channel name (or receiver username)
:param message: chat mesage
:return: FokaBot's response or False if no response
fro -- sender username (for permissions stuff with admin commands)
chan -- channel name
message -- message
return -- fokabot's response string or False
"""
for i in fokabotCommands.commands:
# Loop though all commands
if generalUtils.strContains(message, i["trigger"]):
#if i["trigger"] in message:
if generalUtils.strContains(message.lower(), i["trigger"].lower()):
# message has triggered a command
# Make sure the user has right permissions
@@ -62,4 +63,23 @@ def fokabotResponse(fro, chan, message):
return i["callback"](fro, chan, message[1:])
# No commands triggered
return False
return False
def zingheriLoop():
log.debug("SONO STATI I ZINGHERI, I ZINGHERI!")
clients = glob.streams.getClients("zingheri")
for i in clients:
log.debug(str(i))
if i not in glob.tokens.tokens:
continue
token = glob.tokens.tokens[i]
if token.userID == 999:
continue
if token.zingheri == -1:
chat.sendMessage("FokaBot", token.username, "Knock knock, trick or treat?\nWho are you?\nI'm a {meme}. I'm a little {meme}.\n(reply with 'trick' or 'treat')".format(meme=random.choice(["shavit", "loctaf", "peppy", "foka", "elfo", "nano", "cupola autoportante"])))
token.zingheri = 0
elif token.zingheri == 1:
if token.actionID == actions.PLAYING and (int(time.time()) - token.actionLatestUpdate >= 25):
token.enqueue(serverPackets.zingheri("You'd better give me a treat next time ;)"))
token.leaveStream("zingheri")
threading.Timer(30, zingheriLoop).start()

View File

@@ -1,6 +1,7 @@
"""Global objects and variables"""
import time
from common.ddog import datadogClient
from common.files import fileBuffer, fileLocks
from objects import channelList
@@ -11,27 +12,28 @@ from common.web import schiavo
try:
with open("version") as f:
VERSION = f.read().strip()
VERSION = f.read()
if VERSION == "":
raise Exception
raise
except:
VERSION = "Unknown"
VERSION = "¯\_(xd)_/¯"
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
@@ -43,8 +45,8 @@ gzip = False
localize = False
sentry = False
irc = False
restarting = False
startTime = int(time.time())
streams = streamList.streamList()

View File

@@ -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,18 +23,19 @@ 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
: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
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
"""
self.matchID = matchID
self.streamName = "multi/{}".format(self.matchID)
@@ -48,9 +49,9 @@ class match:
self.beatmapMD5 = beatmapMD5
self.hostUserID = hostUserID
self.gameMode = gameMode
self.matchScoringType = matchScoringTypes.SCORE # default values
self.matchTeamType = matchTeamTypes.HEAD_TO_HEAD # default value
self.matchModMode = matchModModes.NORMAL # default value
self.matchScoringType = matchScoringTypes.score # default values
self.matchTeamType = matchTeamTypes.headToHead # default value
self.matchModMode = matchModModes.normal # default value
self.seed = 0
self.matchDataCache = bytes()
@@ -69,8 +70,6 @@ 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
@@ -110,7 +109,7 @@ class match:
])
# Slot mods if free mod is enabled
if safeMatch.matchModMode == matchModModes.FREE_MOD:
if safeMatch.matchModMode == matchModModes.freeMod:
for i in range(0,16):
struct.append([safeMatch.slots[i].mods, dataTypes.UINT32])
@@ -124,8 +123,7 @@ class match:
"""
Set room host to newHost and send him host packet
:param newHost: new host userID
:return:
newHost -- new host userID
"""
slotID = self.getUserSlotID(newHost)
if slotID is None or self.slots[slotID].user not in glob.tokens.tokens:
@@ -137,21 +135,7 @@ 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):
"""
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:
"""
#self.setSlot(i, slotStatuses.notReady, 0, user, 0)
if status is not None:
self.slots[slotID].status = status
@@ -177,9 +161,8 @@ class match:
"""
Set slotID mods. Same as calling setSlot and then sendUpdate
:param slotID: slot number
:param mods: new mods
:return:
slotID -- slot number
mods -- new mods
"""
# Set new slot data and send update
self.setSlot(slotID, mods=mods)
@@ -191,15 +174,14 @@ class match:
Switch slotID ready/not ready status
Same as calling setSlot and then sendUpdate
:param slotID: slot number
:return:
slotID -- slot number
"""
# Update ready status and setnd update
oldStatus = self.slots[slotID].status
if oldStatus == slotStatuses.READY:
newStatus = slotStatuses.NOT_READY
if oldStatus == slotStatuses.ready:
newStatus = slotStatuses.notReady
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))
@@ -209,14 +191,13 @@ class match:
Lock a slot
Same as calling setSlot and then sendUpdate
:param slotID: slot number
:return:
slotID -- slot number
"""
# 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:
@@ -227,14 +208,13 @@ 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
:param userID: ID of user
:return:
userID -- ID of user
"""
slotID = self.getUserSlotID(userID)
if slotID is None:
@@ -248,7 +228,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
@@ -257,11 +237,7 @@ class match:
self.allPlayersLoaded()
def allPlayersLoaded(self):
"""
Send allPlayersLoaded packet to every playing usr in match
:return:
"""
"""Send allPlayersLoaded packet to every playing usr in match"""
glob.streams.broadcast(self.playingStreamName, serverPackets.allPlayersLoaded())
log.info("MPROOM{}: All players loaded! Match starting...".format(self.matchID))
@@ -269,8 +245,7 @@ class match:
"""
Set a player skip status to True
:param userID: ID of user
:return:
userID -- ID of user
"""
slotID = self.getUserSlotID(userID)
if slotID is None:
@@ -288,7 +263,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
@@ -297,11 +272,7 @@ class match:
self.allPlayersSkipped()
def allPlayersSkipped(self):
"""
Send allPlayersSkipped packet to every playing usr in match
:return:
"""
"""Send allPlayersSkipped packet to every playing usr in match"""
glob.streams.broadcast(self.playingStreamName, serverPackets.allPlayersSkipped())
log.info("MPROOM{}: All players have skipped!".format(self.matchID))
@@ -309,7 +280,7 @@ class match:
"""
Set userID's slot completed to True
:param userID: ID of user
userID -- ID of user
"""
slotID = self.getUserSlotID(userID)
if slotID is None:
@@ -323,7 +294,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
@@ -332,18 +303,15 @@ 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.NOT_READY
if self.slots[i].user is not None and self.slots[i].status == slotStatuses.playing:
self.slots[i].status = slotStatuses.notReady
self.slots[i].loaded = False
self.slots[i].skip = False
self.slots[i].complete = False
@@ -355,7 +323,6 @@ class match:
glob.streams.broadcast(self.streamName, serverPackets.matchComplete())
# Destroy playing stream
glob.streams.dispose(self.playingStreamName)
glob.streams.remove(self.playingStreamName)
# Console output
@@ -365,7 +332,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:
@@ -376,20 +343,21 @@ class match:
"""
Add someone to users in match
:param user: user object of the user
:return: True if join success, False if fail (room is full)
userID -- user id 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.NOT_READY, 0, user.token, 0)
self.setSlot(i, slotStatuses.notReady, 0, user.token, 0)
# Send updated match data
self.sendUpdates()
@@ -404,8 +372,7 @@ class match:
"""
Remove someone from users in match
:param user: user object of the user
:return:
userID -- user if of the user
"""
# Make sure the user is in room
slotID = self.getUserSlotID(user.userID)
@@ -413,7 +380,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:
@@ -440,9 +407,8 @@ class match:
"""
Change userID slot to newSlotID
:param userID: user that changed slot
:param newSlotID: slot id of new slot
:return:
userID -- user that changed slot
newSlotID -- slot id of new slot
"""
# Make sure the user is in room
oldSlotID = self.getUserSlotID(userID)
@@ -450,7 +416,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
@@ -458,7 +424,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)
@@ -473,10 +439,13 @@ class match:
"""
Change match password to newPassword
:param newPassword: new password string
:return:
newPassword -- new password string
"""
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))
@@ -491,8 +460,7 @@ class match:
"""
Set match global mods
:param mods: mods bitwise int thing
:return:
mods -- mods bitwise int thing
"""
# Set new mods and send update
self.mods = mods
@@ -503,9 +471,8 @@ class match:
"""
Set no beatmap status for userID
:param userID: ID of user
:param has: True if has beatmap, false if not
:return:
userID -- ID of user
has -- True if has beatmap, false if not
"""
# Make sure the user is in room
slotID = self.getUserSlotID(userID)
@@ -513,7 +480,7 @@ class match:
return
# Set slot
self.setSlot(slotID, slotStatuses.NO_MAP if not has else slotStatuses.NOT_READY)
self.setSlot(slotID, slotStatuses.noMap if not has else slotStatuses.notReady)
# Send updates
self.sendUpdates()
@@ -522,8 +489,7 @@ class match:
"""
Transfer host to slotID
:param slotID: ID of slot
:return:
slotID -- ID of slot
"""
# Make sure there is someone in that slot
if self.slots[slotID].user is None or self.slots[slotID].user not in glob.tokens.tokens:
@@ -539,8 +505,7 @@ class match:
"""
Send userID's failed packet to everyone in match
:param userID: ID of user
:return:
userID -- ID of user
"""
# Make sure the user is in room
slotID = self.getUserSlotID(userID)
@@ -557,10 +522,10 @@ class match:
"""
Fro invites to in this match.
:param fro: sender userID
:param to: receiver userID
:return:
fro -- sender userID
to -- receiver userID
"""
# Get tokens
froToken = glob.tokens.getTokenFromUserID(fro)
toToken = glob.tokens.getTokenFromUserID(to)
@@ -579,7 +544,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):
@@ -591,8 +556,7 @@ class match:
"""
Change userID's team
:param userID: id of user
:return:
userID -- id of user
"""
# Make sure the user is in room
slotID = self.getUserSlotID(userID)
@@ -600,16 +564,11 @@ 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)
@@ -621,17 +580,16 @@ class match:
"""
Check if match teams are valid
:return: True if valid, False if invalid
:return:
return -- True if valid, False if invalid
"""
if self.matchTeamType != matchTeamTypes.TEAM_VS or self.matchTeamType != matchTeamTypes.TAG_TEAM_VS:
if self.matchTeamType != matchTeamTypes.teamVs or self.matchTeamType != matchTeamTypes.tagTeamVs:
# Teams are always valid if we have no teams
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.NO_MAP) == 0:
if self.slots[i].user is not None and (self.slots[i].status & slotStatuses.noMap) == 0:
if firstTeam == -1:
firstTeam = self.slots[i].team
elif firstTeam != self.slots[i].team:
@@ -642,11 +600,6 @@ 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
@@ -660,8 +613,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

View File

@@ -1,6 +1,7 @@
from objects import match
from objects import glob
from constants import serverPackets
from common.log import logUtils as log
class matchList:
def __init__(self):
@@ -12,14 +13,14 @@ class matchList:
"""
Add a new match to matches list
: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
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
"""
# Add a new match to matches list and create its stream
matchID = self.lastID
@@ -31,19 +32,16 @@ class matchList:
"""
Destroy match object with id = matchID
:param matchID: ID of match to dispose
:return:
matchID -- ID of match to dispose
"""
# Make sure the match exists
if matchID not in self.matches:
return
# Remove match object and stream
_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)
match = self.matches.pop(matchID)
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))

View File

@@ -5,7 +5,6 @@ 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
@@ -13,26 +12,24 @@ 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
:param userID: user associated to this token
:param token_: if passed, set token to that value
if not passed, token will be generated
: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.
userID -- user associated to this token
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.
"""
# 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
@@ -40,7 +37,6 @@ class token:
self.lock = threading.Lock() # Sync primitive
self.streams = []
self.tournament = tournament
self.messagesBuffer = []
# Default variables
self.spectators = []
@@ -55,7 +51,6 @@ 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
@@ -69,6 +64,7 @@ class token:
self.actionText = ""
self.actionMd5 = ""
self.actionMods = 0
self.actionLatestUpdate = self.pingTime
self.gameMode = gameModes.STD
self.beatmapID = 0
self.rankedScore = 0
@@ -78,6 +74,11 @@ class token:
self.gameRank = 0
self.pp = 0
# -1: Non sa dell'esistenza dei zingheri
# 0: In attesa di risposta al messaggiomem
# 1: zingheri soonTM
self.zingheri = -1
# Generate/set token
if token_ is not None:
self.token = token_
@@ -93,67 +94,57 @@ class token:
# Join main stream
self.joinStream("main")
self.joinStream("zingheri")
def enqueue(self, bytes_):
"""
Add bytes (packets) to queue
:param bytes_: (packet) bytes to enqueue
bytes -- (packet) bytes to enqueue
"""
if not self.irc:
if len(bytes_) < 10 * 10 ** 6:
self.queue += bytes_
else:
log.warning("{}'s packets buffer is above 10M!! Lost some data!".format(self.username))
# 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, channelObject):
"""
Join a channel
:param channelObject: channel object
:raises: exceptions.userAlreadyInChannelException()
exceptions.channelNoPermissionsException()
def joinChannel(self, 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))
Add channel to joined channels list
def partChannel(self, channelObject):
channel -- channel name
"""
if channel not in self.joinedChannels:
self.joinedChannels.append(channel)
def partChannel(self, channel):
"""
Remove channel from joined channels list
:param channelObject: channel object
channel -- channel name
"""
self.joinedChannels.remove(channelObject.name)
self.leaveStream("chat/{}".format(channelObject.name))
if channel in self.joinedChannels:
self.joinedChannels.remove(channel)
def setLocation(self, latitude, longitude):
def setLocation(self, location):
"""
Set client location
Set location (latitude and longitude)
:param latitude: latitude
:param longitude: longitude
location -- [latitude, longitude]
"""
self.location = (latitude, longitude)
self.location = location
def getLatitude(self):
"""
Get latitude
:return: latitude
return -- latitude
"""
return self.location[0]
@@ -161,16 +152,15 @@ class token:
"""
Get longitude
:return: longitude
return -- longitude
"""
return self.location[1]
def startSpectating(self, host):
"""
Set the spectating user to userID, join spectator stream and chat channel
and send required packets to host
Set the spectating user to userID
:param host: host osuToken object
user -- user object
"""
# Stop spectating old client
self.stopSpectating()
@@ -210,12 +200,6 @@ 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
@@ -244,30 +228,46 @@ 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 updatePingTime(self):
def setCountry(self, countryID):
"""
Update latest ping time to current time
Set country to countryID
:return:
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"""
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
:param matchID: new match ID
:return:
matchID -- new match ID
"""
# Make sure the match exists
if matchID not in glob.matches.matches:
@@ -327,10 +327,7 @@ class token:
"""
Kick this user from the server
:param message: Notification message to send to this user.
Default: "You have been kicked from the server. Please login again."
:param reason: Kick reason, used in logs. Default: "kick"
:return:
message -- Notification message to send to this user. Optional.
"""
# Send packet to target
log.info("{} has been disconnected. ({})".format(self.username, reason))
@@ -339,28 +336,21 @@ class token:
self.enqueue(serverPackets.loginFailed())
# Logout event
logoutEvent.handle(self, deleteToken=self.irc)
logoutEvent.handle(self, None)
def silence(self, seconds = None, reason = "", author = 999):
def silence(self, seconds, reason, author = 999):
"""
Silences this user (db, packet and token)
: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:
seconds -- silence length in seconds
reason -- silence reason
author -- userID of who has silenced the target. Optional. Default: 999 (fokabot)
"""
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
userUtils.silence(self.userID, seconds, reason, author)
# Silence in db and token
self.silenceEndTime = int(time.time())+seconds
userUtils.silence(self.userID, seconds, reason, author)
# Silence token
self.silenceEndTime = int(time.time()) + seconds
# Send silence packet to user
# Send silence packet to target
self.enqueue(serverPackets.silenceEndTime(seconds))
# Send silenced packet to everyone else
@@ -370,8 +360,7 @@ class token:
"""
Silences the user if is spamming.
:param increaseSpamRate: set to True if the user has sent a new message. Default: True
:return:
increaseSpamRate -- pass True if the user has sent a new message. Optional. Default: True
"""
# Increase the spam rate if needed
if increaseSpamRate:
@@ -385,7 +374,7 @@ class token:
"""
Returns True if this user is silenced, otherwise False
:return: True if this user is silenced, otherwise False
return -- True/False
"""
return self.silenceEndTime-int(time.time()) > 0
@@ -394,16 +383,12 @@ class token:
Returns the seconds left for this user's silence
(0 if user is not silenced)
:return: silence seconds left (or 0)
return -- silence seconds left
"""
return max(0, self.silenceEndTime-int(time.time()))
def updateCachedStats(self):
"""
Update all cached stats for this token
:return:
"""
"""Update all cached stats for this token"""
stats = userUtils.getUserStats(self.userID, self.gameMode)
log.debug(str(stats))
if stats is None:
@@ -416,110 +401,36 @@ class token:
self.gameRank = stats["gameRank"]
self.pp = stats["pp"]
def checkRestricted(self):
def checkRestricted(self, force=False):
"""
Check if this token is restricted. If so, send fokabot message
:return:
force -- If True, get restricted value from db.
If false, get the cached one. Optional. Default: False
"""
oldRestricted = self.restricted
self.restricted = userUtils.isRestricted(self.userID)
if force:
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.")
chat.sendMessage("FokaBot",self.username, "Your account is currently in restricted mode. Please visit ripple's website for more information.")
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)
self.leaveStream(i)

View File

@@ -43,29 +43,15 @@ class stream:
log.info("{} has left stream {}".format(token, self.name))
self.clients.remove(token)
def broadcast(self, data, but=None):
def broadcast(self, data):
"""
Send some data to all (or some) clients connected to this stream
Send some data to all clients connected to this stream
:param data: data to send
:param 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)
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].leaveStream(self.name)
self.removeClient(token=i)

View File

@@ -1,7 +1,6 @@
from objects import stream
from objects import glob
# TODO: use *args and **kwargs
class streamList:
def __init__(self):
self.streams = {}
@@ -56,39 +55,25 @@ class streamList:
return
self.streams[streamName].removeClient(client=client, token=token)
def broadcast(self, streamName, data, but=None):
def broadcast(self, streamName, data):
"""
Send some data to all clients in a stream
: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, but)
self.streams[streamName].broadcast(data)
def dispose(self, streamName, *args, **kwargs):
def getClients(self, streamName):
"""
Call `dispose` on `streamName`
Get all clients in a stream
:param streamName: name of the stream
:param args:
:param kwargs:
:return:
"""
if streamName not in self.streams:
return
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
return self.streams[streamName].clients

View File

@@ -1,8 +1,6 @@
import threading
import time
import redis
from common.ripple import userUtils
from common.log import logUtils as log
from constants import serverPackets
@@ -10,45 +8,52 @@ 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
: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
userID -- user id associated to that token
irc -- if True, set this token as IRC client
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
:param token: token string
:return:
token -- token string
"""
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
:param token: token to find
:return: False if not found, userID if found
token -- token to find
return -- false if not found, userID if found
"""
# Make sure the token exists
if token not in self.tokens:
@@ -57,91 +62,63 @@ class tokenList:
# Get userID associated to that token
return self.tokens[token].userID
def getTokenFromUserID(self, userID, ignoreIRC=False, _all=False):
def getTokenFromUserID(self, userID, ignoreIRC=False):
"""
Get token from a user ID
: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
userID -- user ID to find
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 value
# Return full list or None if not found
if _all:
return ret
else:
return None
# Return none if not found
return None
def getTokenFromUsername(self, username, ignoreIRC=False, safe=False, _all=False):
def getTokenFromUsername(self, username, ignoreIRC=False):
"""
Get an osuToken object from an username
Get token from a username
:param username: normal username or safe username
: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
username -- username to find
return -- False if not found, token object if found
"""
# lowercase
who = username.lower() if not safe else username
who = username.lower()
# Make sure the token exists
ret = []
for _, value in self.tokens.items():
if (not safe and value.username.lower() == who) or (safe and value.safeUsername == who):
if value.username.lower() == who:
if ignoreIRC and value.irc:
continue
if _all:
ret.append(value)
else:
return value
return value
# Return full list or None if not found
if _all:
return ret
else:
return None
# Return none if not found
return None
def deleteOldTokens(self, userID):
"""
Delete old userID's tokens if found
:param userID: tokens associated to this user will be deleted
:return:
userID -- tokens associated to this user will be deleted
"""
# 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")
delete.append(self.tokens[key])
for i in delete:
logoutEvent.handle(i)
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")
def multipleEnqueue(self, packet, who, but = False):
"""
Enqueue a packet to multiple users
:param packet: packet bytes to enqueue
:param who: userIDs array
:param but: if True, enqueue to everyone but users in `who` array
:return:
packet -- packet bytes to enqueue
who -- userIDs array
but -- if True, enqueue to everyone but users in who array
"""
for _, value in self.tokens.items():
shouldEnqueue = False
@@ -157,21 +134,19 @@ class tokenList:
"""
Enqueue packet(s) to every connected user
:param packet: packet bytes to enqueue
:return:
packet -- packet bytes to enqueue
"""
for _, value in self.tokens.items():
value.enqueue(packet)
def usersTimeoutCheckLoop(self, timeoutTime = 100, checkTime = 100):
"""
Start timed out users disconnect loop.
This function will be called every `checkTime` seconds and so on, forever.
Deletes all timed out users.
If called once, will recall after checkTime seconds and so on, forever
CALL THIS FUNCTION ONLY ONCE!
:param timeoutTime: seconds of inactivity required to disconnect someone. Default: 100
:param checkTime: seconds between loops. Default: 100
:return:
timeoutTime - seconds of inactivity required to disconnect someone (Default: 100)
checkTime - seconds between loops (Default: 100)
"""
log.debug("Checking timed out clients")
timedOutTokens = [] # timed out users
@@ -195,11 +170,8 @@ class tokenList:
def spamProtectionResetLoop(self):
"""
Start spam protection reset loop.
Called every 10 seconds.
Reset spam rate every 10 seconds.
CALL THIS FUNCTION ONLY ONCE!
:return:
"""
# Reset spamRate for every token
for _, value in self.tokens.items():
@@ -210,28 +182,22 @@ class tokenList:
def deleteBanchoSessions(self):
"""
Remove all `peppy:sessions:*` redis keys.
Truncate bancho_sessions table.
Call at bancho startup to delete old cached sessions
:return:
"""
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
glob.db.execute("TRUNCATE TABLE bancho_sessions")
def tokenExists(self, username = "", userID = -1):
"""
Check if a token exists
Use username or userid, not both at the same time.
Check if a token exists (aka check if someone is connected)
:param username: Optional.
:param userID: Optional.
:return: True if it exists, otherwise False
username -- Optional.
userID -- Optional.
return -- True if it exists, otherwise False
Use username or userid, not both at the same time.
"""
if userID > -1:
return True if self.getTokenFromUserID(userID) is not None else False
else:
return True if self.getTokenFromUsername(username) is not None else False
return True if self.getTokenFromUsername(username) is not None else False

91
pep.py
View File

@@ -1,20 +1,21 @@
"""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.redis import pubSub
from common.ripple import userUtils
from common.web import schiavo
from handlers import apiFokabotMessageHandler
from handlers import apiIsOnlineHandler
@@ -32,13 +33,7 @@ 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([
@@ -87,7 +82,7 @@ if __name__ == "__main__":
# Connect to db
try:
consoleHelper.printNoNl("> Connecting to MySQL database... ")
consoleHelper.printNoNl("> Connecting to MySQL database...")
glob.db = dbConnector.db(glob.conf.config["db"]["host"], glob.conf.config["db"]["username"], glob.conf.config["db"]["password"], glob.conf.config["db"]["database"], int(glob.conf.config["db"]["workers"]))
consoleHelper.printNoNl(" ")
consoleHelper.printDone()
@@ -97,31 +92,6 @@ if __name__ == "__main__":
consoleHelper.printColored("[!] Error while connection to database. Please check your config.ini and run the server again", bcolors.RED)
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... ")
@@ -142,7 +112,7 @@ if __name__ == "__main__":
consoleHelper.printNoNl("> Creating threads pool... ")
glob.pool = ThreadPool(int(glob.conf.config["server"]["threads"]))
consoleHelper.printDone()
except ValueError:
except:
consoleHelper.printError()
consoleHelper.printColored("[!] Error while creating threads pool. Please check your config.ini and run the server again", bcolors.RED)
@@ -155,11 +125,6 @@ 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()
@@ -169,6 +134,12 @@ if __name__ == "__main__":
consoleHelper.printNoNl("> Creating packets streams... ")
glob.streams.add("main")
glob.streams.add("lobby")
glob.streams.add("zingheri")
consoleHelper.printDone()
# Start fokabot
consoleHelper.printNoNl("> Connecting FokaBot... ")
fokabot.connect()
consoleHelper.printDone()
# Initialize user timeout check loop
@@ -181,6 +152,16 @@ if __name__ == "__main__":
glob.tokens.spamProtectionResetLoop()
consoleHelper.printDone()
# Initialize zingheri loop
consoleHelper.printNoNl("> WOO WOO... ")
fokabot.zingheriLoop()
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:
@@ -188,7 +169,7 @@ if __name__ == "__main__":
# Discord
if generalUtils.stringToBool(glob.conf.config["discord"]["enable"]):
glob.schiavo = schiavo.schiavo(glob.conf.config["discord"]["boturl"], "**pep.py**")
glob.schiavo = schiavo.schiavo(glob.conf.config["discord"]["boturl"])
else:
consoleHelper.printColored("[!] Warning! Discord logging is disabled!", bcolors.YELLOW)
@@ -227,17 +208,6 @@ 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)
@@ -248,10 +218,9 @@ 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 ValueError:
except:
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)
@@ -260,27 +229,15 @@ 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 ValueError:
except:
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()

View File

@@ -1,18 +0,0 @@
from common.redis import generalPubSubHandler
from common.ripple import userUtils
from objects import glob
class handler(generalPubSubHandler.generalPubSubHandler):
def __init__(self):
super().__init__()
self.type = "int"
def handle(self, userID):
userID = super().parseData(userID)
if userID is None:
return
targetToken = glob.tokens.getTokenFromUserID(userID)
if targetToken is not None:
targetToken.privileges = userUtils.getPrivileges(userID)
targetToken.checkBanned()
targetToken.checkRestricted()

View File

@@ -1,50 +0,0 @@
from common.redis import generalPubSubHandler
from common.ripple import userUtils
from common.log import logUtils as log
from common.constants import actions
from objects import glob
def handleUsernameChange(userID, newUsername, targetToken=None):
try:
userUtils.appendNotes(userID, "-- Username change: '{}' -> '{}'".format(userUtils.getUsername(userID), newUsername))
userUtils.changeUsername(userID, newUsername=newUsername)
if targetToken is not None:
targetToken.kick("Your username has been changed to {}. Please log in again.".format(newUsername), "username_change")
except userUtils.usernameAlreadyInUseError:
log.rap(999, "Username change: {} is already in use!", through="Bancho")
if targetToken is not None:
targetToken.kick("There was a critical error while trying to change your username. Please contact a developer.", "username_change_fail")
except userUtils.invalidUsernameError:
log.rap(999, "Username change: {} is not a valid username!", through="Bancho")
if targetToken is not None:
targetToken.kick("There was a critical error while trying to change your username. Please contact a developer.", "username_change_fail")
class handler(generalPubSubHandler.generalPubSubHandler):
def __init__(self):
super().__init__()
self.structure = {
"userID": 0,
"newUsername": ""
}
def handle(self, data):
data = super().parseData(data)
if data is None:
return
# Get the user's token
targetToken = glob.tokens.getTokenFromUserID(data["userID"])
if targetToken is None:
# If the user is offline change username immediately
handleUsernameChange(data["userID"], data["newUsername"])
else:
if targetToken.irc or (targetToken.actionID != actions.PLAYING and targetToken.actionID != actions.MULTIPLAYING):
# If the user is online and he's connected through IRC or he's not playing,
# change username and kick the user immediately
handleUsernameChange(data["userID"], data["newUsername"], targetToken)
else:
# If the user is playing, delay the username change until he submits the score
# On submit modular, lets will send the username change request again
# through redis once the score has been submitted
# The check is performed on bancho logout too, so if the user disconnects
# without submitting a score, the username gets changed on bancho logout
glob.redis.set("ripple:change_username_pending:{}".format(data["userID"]), data["newUsername"])

View File

@@ -1,18 +0,0 @@
from common.redis import generalPubSubHandler
from objects import glob
class handler(generalPubSubHandler.generalPubSubHandler):
def __init__(self):
super().__init__()
self.structure = {
"userID": 0,
"reason": ""
}
def handle(self, data):
data = super().parseData(data)
if data is None:
return
targetToken = glob.tokens.getTokenFromUserID(data["userID"])
if targetToken is not None:
targetToken.kick(data["reason"], "pubsub_kick")

View File

@@ -1,19 +0,0 @@
from common.redis import generalPubSubHandler
from objects import glob
from constants import serverPackets
class handler(generalPubSubHandler.generalPubSubHandler):
def __init__(self):
super().__init__()
self.structure = {
"userID": 0,
"message": ""
}
def handle(self, data):
data = super().parseData(data)
if data is None:
return
targetToken = glob.tokens.getTokenFromUserID(data["userID"])
if targetToken is not None:
targetToken.enqueue(serverPackets.notification(data["message"]))

View File

@@ -1,15 +0,0 @@
from common.redis import generalPubSubHandler
from objects import glob
class handler(generalPubSubHandler.generalPubSubHandler):
def __init__(self):
super().__init__()
self.type = "int"
def handle(self, userID):
userID = super().parseData(userID)
if userID is None:
return
targetToken = glob.tokens.getTokenFromUserID(userID)
if targetToken is not None:
targetToken.silence()

View File

@@ -1,15 +0,0 @@
from common.redis import generalPubSubHandler
from objects import glob
class handler(generalPubSubHandler.generalPubSubHandler):
def __init__(self):
super().__init__()
self.type = "int"
def handle(self, userID):
userID = super().parseData(userID)
if userID is None:
return
targetToken = glob.tokens.getTokenFromUserID(userID)
if targetToken is not None:
targetToken.updateCachedStats()

View File

@@ -3,8 +3,4 @@ tornado
mysqlclient
psutil
raven
bcrypt>=3.1.1
dill
redis
cython
datadog
bcrypt

View File

@@ -1,17 +0,0 @@
"""Cython build file"""
from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize
import os
cythonExt = []
for root, dirs, files in os.walk(os.getcwd()):
for file in files:
if file.endswith(".pyx"):
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),
)

View File

@@ -1 +1 @@
1.11.2
1.9.0