.BANCHO. Add pubsub handlers for username changes, bans, restrictions, silences, stats update, kicks and bancho settings reload.

This commit is contained in:
Nyo 2016-11-20 11:31:51 +01:00
parent fb00063e0f
commit aa32e8bea6
13 changed files with 220 additions and 42 deletions

View File

@ -304,22 +304,7 @@ def systemShutdown(fro, chan, message):
return restartShutdown(False) return restartShutdown(False)
def systemReload(fro, chan, message): def systemReload(fro, chan, message):
# Reload settings from bancho_settings glob.banchoConf.reload()
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!" return "Bancho settings reloaded!"
def systemMaintenance(fro, chan, message): def systemMaintenance(fro, chan, message):

View File

@ -5,20 +5,19 @@ from constants import clientPackets
from constants import serverPackets from constants import serverPackets
from objects import glob from objects import glob
def handle(userToken, packetData): def handle(userToken, packetData):
# Get usertoken data # Get usertoken data
userID = userToken.userID userID = userToken.userID
username = userToken.username username = userToken.username
# Make sure we are not banned # Make sure we are not banned
if userUtils.isBanned(userID): #if userUtils.isBanned(userID):
userToken.enqueue(serverPackets.loginBanned()) # userToken.enqueue(serverPackets.loginBanned())
return # return
# Send restricted message if needed # Send restricted message if needed
if userToken.restricted: #if userToken.restricted:
userToken.checkRestricted(True) # userToken.checkRestricted(True)
# Change action packet # Change action packet
packetData = clientPackets.userActionChange(packetData) packetData = clientPackets.userActionChange(packetData)
@ -34,8 +33,10 @@ if userToken.matchID != -1 and userToken.actionID != actions.MULTIPLAYING and us
''' '''
# Update cached stats if our pp changed if we've just submitted a score or we've changed gameMode # Update cached stats if our pp changed if we've just submitted a score or we've changed gameMode
if (userToken.actionID == actions.PLAYING or userToken.actionID == actions.MULTIPLAYING) or (userToken.pp != userUtils.getPP(userID, userToken.gameMode)) or (userToken.gameMode != packetData["gameMode"]): #if (userToken.actionID == actions.PLAYING or userToken.actionID == actions.MULTIPLAYING) or (userToken.pp != userUtils.getPP(userID, userToken.gameMode)) or (userToken.gameMode != packetData["gameMode"]):
# Always update game mode, or we'll cache stats from the wrong game mode if we've changed it
# Update cached stats if we've changed gamemode
if userToken.gameMode != packetData["gameMode"]:
userToken.gameMode = packetData["gameMode"] userToken.gameMode = packetData["gameMode"]
userToken.updateCachedStats() userToken.updateCachedStats()

View File

@ -1,4 +1,5 @@
import time import time
import json
from common.log import logUtils as log from common.log import logUtils as log
from constants import serverPackets from constants import serverPackets
@ -6,7 +7,7 @@ from helpers import chatHelper as chat
from objects import glob from objects import glob
def handle(userToken, _=None): def handle(userToken, _=None, deleteToken=True):
# get usertoken data # get usertoken data
userID = userToken.userID userID = userToken.userID
username = userToken.username username = userToken.username
@ -38,7 +39,19 @@ def handle(userToken, _=None):
glob.ircServer.forceDisconnection(userToken.username) glob.ircServer.forceDisconnection(userToken.username)
# Delete token # Delete token
glob.tokens.deleteToken(requestToken) if deleteToken:
glob.tokens.deleteToken(requestToken)
else:
userToken.kicked = True
# Change username if needed
newUsername = glob.redis.get("ripple:change_username_pending:{}".format(userID))
if newUsername is not None:
log.debug("Sending username change request for user {}".format(userID))
glob.redis.publish("peppy:change_username", json.dumps({
"userID": userID,
"newUsername": newUsername.decode("utf-8")
}))
# Console output # Console output
log.info("{} has been disconnected. (logout)".format(username)) log.info("{} has been disconnected. (logout)".format(username))

View File

@ -122,7 +122,7 @@ class handler(SentryMixin, requestsManager.asyncRequestHandler):
packetIDs.client_userStatsRequest: handleEvent(userStatsRequestEvent), packetIDs.client_userStatsRequest: handleEvent(userStatsRequestEvent),
packetIDs.client_requestStatusUpdate: handleEvent(requestStatusUpdateEvent), packetIDs.client_requestStatusUpdate: handleEvent(requestStatusUpdateEvent),
packetIDs.client_userPanelRequest: handleEvent(userPanelRequestEvent), packetIDs.client_userPanelRequest: handleEvent(userPanelRequestEvent),
packetIDs.client_channelJoin: handleEvent(channelJoinEvent), packetIDs.client_channelJoin: handleEvent(channelJoinEvent),
packetIDs.client_channelPart: handleEvent(channelPartEvent), packetIDs.client_channelPart: handleEvent(channelPartEvent),
packetIDs.client_sendPublicMessage: handleEvent(sendPublicMessageEvent), packetIDs.client_sendPublicMessage: handleEvent(sendPublicMessageEvent),
@ -206,6 +206,9 @@ class handler(SentryMixin, requestsManager.asyncRequestHandler):
userToken.updatePingTime() userToken.updatePingTime()
# Release token lock # Release token lock
userToken.lock.release() userToken.lock.release()
# Delete token if kicked
if userToken.kicked:
glob.tokens.deleteToken(userToken)
if glob.outputRequestTime: if glob.outputRequestTime:
# End time # End time

View File

@ -1,5 +1,6 @@
# TODO: Rewrite this shit # TODO: Rewrite this shit
from common import generalUtils from common import generalUtils
from constants import serverPackets
from objects import glob from objects import glob
@ -41,3 +42,20 @@ class banchoConfig:
""" """
self.config["banchoMaintenance"] = maintenance self.config["banchoMaintenance"] = maintenance
glob.db.execute("UPDATE bancho_settings SET value_int = %s WHERE name = 'bancho_maintenance'", [int(maintenance)]) glob.db.execute("UPDATE bancho_settings SET value_int = %s WHERE name = 'bancho_maintenance'", [int(maintenance)])
def reload(self):
# Reload settings from bancho_settings
glob.banchoConf.loadSettings()
# Reload channels too
glob.channels.loadChannels()
# And chat filters
glob.chatFilters.loadFilters()
# Send new channels and new bottom icon to everyone
glob.streams.broadcast("main", serverPackets.mainMenuIcon(glob.banchoConf.config["menuIcon"]))
glob.streams.broadcast("main", serverPackets.channelInfoEnd())
for key, value in glob.channels.channels.items():
if value.publicRead == True and value.hidden == False:
glob.streams.broadcast("main", serverPackets.channelInfo(key))

View File

@ -31,6 +31,7 @@ class token:
self.privileges = userUtils.getPrivileges(self.userID) self.privileges = userUtils.getPrivileges(self.userID)
self.admin = userUtils.isInPrivilegeGroup(self.userID, "developer") or userUtils.isInPrivilegeGroup(self.userID, "community manager") self.admin = userUtils.isInPrivilegeGroup(self.userID, "developer") or userUtils.isInPrivilegeGroup(self.userID, "community manager")
self.irc = irc self.irc = irc
self.kicked = False
self.restricted = userUtils.isRestricted(self.userID) self.restricted = userUtils.isRestricted(self.userID)
self.loginTime = int(time.time()) self.loginTime = int(time.time())
self.pingTime = self.loginTime self.pingTime = self.loginTime
@ -323,22 +324,28 @@ class token:
self.enqueue(serverPackets.loginFailed()) self.enqueue(serverPackets.loginFailed())
# Logout event # Logout event
logoutEvent.handle(self, None) logoutEvent.handle(self, deleteToken=False)
def silence(self, seconds, reason, author = 999): def silence(self, seconds = None, reason = "", author = 999):
""" """
Silences this user (db, packet and token) Silences this user (db, packet and token)
:param seconds: silence length in seconds :param seconds: silence length in seconds. If None, get it from db. Default: None
:param reason: silence reason :param reason: silence reason. Default: empty string
:param author: userID of who has silenced the user. Default: 999 (FokaBot) :param author: userID of who has silenced the user. Default: 999 (FokaBot)
:return: :return:
""" """
# Silence in db and token if seconds is None:
self.silenceEndTime = int(time.time())+seconds # Get silence expire from db if needed
userUtils.silence(self.userID, seconds, reason, author) seconds = max(0, userUtils.getSilenceEnd(self.userID) - int(time.time()))
else:
# Silence in db and token
userUtils.silence(self.userID, seconds, reason, author)
# Send silence packet to target # Silence token
self.silenceEndTime = int(time.time()) + seconds
# Send silence packet to user
self.enqueue(serverPackets.silenceEndTime(seconds)) self.enqueue(serverPackets.silenceEndTime(seconds))
# Send silenced packet to everyone else # Send silenced packet to everyone else
@ -394,18 +401,29 @@ class token:
self.gameRank = stats["gameRank"] self.gameRank = stats["gameRank"]
self.pp = stats["pp"] self.pp = stats["pp"]
def checkRestricted(self, force=False): def checkRestricted(self):
""" """
Check if this token is restricted. If so, send fokabot message Check if this token is restricted. If so, send fokabot message
:param force: If True, get restricted value from db.
If False, get the cached one. Default: False
:return: :return:
""" """
if force: oldRestricted = self.restricted
self.restricted = userUtils.isRestricted(self.userID) self.restricted = userUtils.isRestricted(self.userID)
if self.restricted: if self.restricted:
self.setRestricted() self.setRestricted()
elif not self.restricted and oldRestricted != self.restricted:
self.resetRestricted()
def checkBanned(self):
"""
Check if this user is banned. If so, disconnect it.
:return:
"""
if userUtils.isBanned(self.userID):
self.enqueue(serverPackets.loginBanned())
logoutEvent.handle(self, deleteToken=False)
def setRestricted(self): def setRestricted(self):
""" """
@ -415,7 +433,16 @@ class token:
:return: :return:
""" """
self.restricted = True self.restricted = True
chat.sendMessage("FokaBot",self.username, "Your account is currently in restricted mode. Please visit ripple's website for more information.") chat.sendMessage("FokaBot", self.username, "Your account is currently in restricted mode. Please visit ripple's website for more information.")
def resetRestricted(self):
"""
Send FokaBot message to alert the user that he has been unrestricted
and he has to log in again.
:return:
"""
chat.sendMessage("FokaBot", self.username, "Your account has been unrestricted! Please log in again.")
def joinStream(self, name): def joinStream(self, name):
""" """

18
pep.py
View File

@ -16,7 +16,7 @@ from common.constants import bcolors
from common.db import dbConnector from common.db import dbConnector
from common.ddog import datadogClient from common.ddog import datadogClient
from common.log import logUtils as log from common.log import logUtils as log
from common.ripple import userUtils from common.redis import pubSub
from common.web import schiavo from common.web import schiavo
from handlers import apiFokabotMessageHandler from handlers import apiFokabotMessageHandler
from handlers import apiIsOnlineHandler from handlers import apiIsOnlineHandler
@ -34,6 +34,12 @@ from objects import banchoConfig
from objects import chatFilters from objects import chatFilters
from objects import fokabot from objects import fokabot
from objects import glob from objects import glob
from pubSubHandlers import changeUsernameHandler
from pubSubHandlers import disconnectHandler
from pubSubHandlers import banHandler
from pubSubHandlers import updateSilenceHandler
from pubSubHandlers import updateStatsHandler
def make_app(): def make_app():
@ -264,6 +270,16 @@ if __name__ == "__main__":
log.logMessage("**pep.py** Server started!", discord="bunker", of="info.txt", stdout=False) log.logMessage("**pep.py** Server started!", discord="bunker", of="info.txt", stdout=False)
consoleHelper.printColored("> Tornado listening for HTTP(s) clients on 127.0.0.1:{}...".format(serverPort), bcolors.GREEN) consoleHelper.printColored("> Tornado listening for HTTP(s) clients on 127.0.0.1:{}...".format(serverPort), bcolors.GREEN)
# Connect to pubsub channels
pubSub.listener(glob.redis, {
"peppy:disconnect": disconnectHandler.handler(),
"peppy:change_username": changeUsernameHandler.handler(),
"peppy:reload_settings": lambda x: x == b"reload" and glob.banchoConf.reload(),
"peppy:update_cached_stats": updateStatsHandler.handler(),
"peppy:silence": updateSilenceHandler.handler(),
"peppy:ban": banHandler.handler(),
}).start()
# Start tornado # Start tornado
glob.application.listen(serverPort) glob.application.listen(serverPort)
tornado.ioloop.IOLoop.instance().start() tornado.ioloop.IOLoop.instance().start()

View File

View File

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

View File

@ -0,0 +1,49 @@
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.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

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

View File

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

View File

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