pep.py/helpers/chatHelper.py

410 lines
13 KiB
Python
Raw Normal View History

2016-10-02 20:48:14 +00:00
from common.log import logUtils as log
from common.ripple import userUtils
from constants import exceptions
2016-10-02 20:48:14 +00:00
from constants import messageTemplates
from constants import serverPackets
from events import logoutEvent
2016-10-02 20:48:14 +00:00
from objects import fokabot
from objects import glob
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
"""
try:
# Get token if not defined
2016-09-02 15:45:10 +00:00
if token is None:
token = glob.tokens.getTokenFromUserID(userID)
# Make sure the token exists
2016-09-02 15:45:10 +00:00
if token is None:
raise exceptions.userNotFoundException
else:
token = token
userID = token.userID
# Get usertoken data
username = token.username
# Normal channel, do check stuff
# Make sure the channel exists
if channel not in glob.channels.channels:
raise exceptions.channelUnknownException
# Check channel permissions
channelObject = glob.channels.channels[channel]
if channelObject.publicRead == False and token.admin == False:
raise exceptions.channelNoPermissionsException
# Add our userID to users in that channel
channelObject.userJoin(userID)
# Add the channel to our joined channel
token.joinChannel(channel)
# Send channel joined (bancho). We use clientName here because of #multiplayer and #spectator channels
token.enqueue(serverPackets.channelJoinSuccess(userID, channelObject.clientName))
# Send channel joined (IRC)
if glob.irc == True and toIRC == True:
glob.ircServer.banchoJoinChannel(username, channel)
# Console output
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(username, channel))
return 403
except exceptions.channelUnknownException:
log.warning("{} attempted to join an unknown channel ({})".format(username, channel))
return 403
except exceptions.userNotFoundException:
log.warning("User not connected to IRC/Bancho")
return 403 # idk
def partChannel(userID = 0, channel = "", token = None, toIRC = True, kick = False):
"""
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
"""
try:
# Get token if not defined
2016-09-02 15:45:10 +00:00
if token is None:
token = glob.tokens.getTokenFromUserID(userID)
# Make sure the token exists
2016-09-02 15:45:10 +00:00
if token is None:
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)
channelClient = channel
if channel == "#spectator":
if token.spectating is None:
s = userID
else:
2016-10-07 12:13:42 +00:00
s = token.spectatingUserID
channel = "#spect_{}".format(s)
elif channel == "#multiplayer":
channel = "#multi_{}".format(token.matchID)
elif channel.startswith("#spect_"):
channelClient = "#spectator"
elif channel.startswith("#multi_"):
channelClient = "#multiplayer"
# Make sure the channel exists
if channel not in glob.channels.channels:
raise exceptions.channelUnknownException
# Part channel (token-side and channel-side)
channelObject = glob.channels.channels[channel]
token.partChannel(channel)
channelObject.userPart(userID)
# Force close tab if needed
# NOTE: Maybe always needed, will check later
2016-09-02 15:45:10 +00:00
if kick:
token.enqueue(serverPackets.channelKicked(channelClient))
# IRC part
if glob.irc == True and toIRC == True:
glob.ircServer.banchoPartChannel(username, channel)
# Console output
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(username, channel))
return 403
except exceptions.userNotFoundException:
log.warning("User not connected to IRC/Bancho")
return 442 # idk
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
"""
try:
tokenString = ""
# Get token object if not passed
2016-09-02 15:45:10 +00:00
if token is None:
token = glob.tokens.getTokenFromUsername(fro)
2016-09-02 15:45:10 +00:00
if token is None:
raise exceptions.userNotFoundException
else:
# token object alredy passed, get its string and its username (fro)
fro = token.username
tokenString = token.token
# Set some variables
userID = token.userID
username = token.username
# Make sure this is not a tournament client
if token.tournament:
raise exceptions.userTournamentException()
# Make sure the user is not in restricted mode
2016-09-02 15:45:10 +00:00
if token.restricted:
raise exceptions.userRestrictedException()
# Make sure the user is not silenced
2016-09-02 15:45:10 +00:00
if token.isSilenced():
raise exceptions.userSilencedException()
# Determine internal name if needed
# (toclient is used clientwise for #multiplayer and #spectator channels)
toClient = to
if to == "#spectator":
if token.spectating is None:
s = userID
else:
2016-10-07 12:13:42 +00:00
s = token.spectatingUserID
to = "#spect_{}".format(s)
elif to == "#multiplayer":
to = "#multi_{}".format(token.matchID)
elif to.startswith("#spect_"):
toClient = "#spectator"
elif to.startswith("#multi_"):
toClient = "#multiplayer"
# Truncate message if > 2048 characters
message = message[:2048]+"..." if len(message) > 2048 else message
2016-08-10 10:00:33 +00:00
# Check for word filters
message = glob.chatFilters.filterMessage(message)
2016-08-08 03:19:52 +00:00
# Build packet bytes
packet = serverPackets.sendMessage(username, toClient, message)
# Send the message
isChannel = to.startswith("#")
2016-09-02 15:45:10 +00:00
if isChannel:
# CHANNEL
# Make sure the channel exists
if to not in glob.channels.channels:
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
# Make sure we have write permissions
if glob.channels.channels[to].publicWrite == False and token.admin == False:
raise exceptions.channelNoPermissionsException
# Everything seems fine, build recipients list and send packet
recipients = glob.channels.channels[to].connectedUsers[:]
for key, value in glob.tokens.tokens.items():
# Skip our client and irc clients
if key == tokenString or value.irc == True:
continue
# Send to this client if it's connected to the channel
if value.userID in recipients:
value.enqueue(packet)
else:
# USER
# Make sure recipient user is connected
recipientToken = glob.tokens.getTokenFromUsername(to)
2016-09-02 15:45:10 +00:00
if recipientToken is None:
raise exceptions.userNotFoundException
# Make sure the recipient is not a tournament client
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":
raise exceptions.userRestrictedException()
# TODO: Make sure the recipient has not disabled PMs for non-friends or he's our friend
# Away check
if recipientToken.awayCheck(userID):
sendMessage(to, fro, "\x01ACTION is away: {message}\x01".format(code=chr(int(1)), message=recipientToken.awayMessage))
# Check message templates (mods/admins only)
if message in messageTemplates.templates and token.admin == True:
sendMessage(fro, to, messageTemplates.templates[message])
# Everything seems fine, send packet
recipientToken.enqueue(packet)
# Send the message to IRC
if glob.irc == True and toIRC == True:
glob.ircServer.banchoMessage(fro, to, message)
# Spam protection (ignore FokaBot)
if userID > 999:
token.spamProtection()
# Fokabot message
if isChannel == True or to.lower() == "fokabot":
fokaMessage = fokabot.fokabotResponse(username, to, message)
2016-09-02 15:45:10 +00:00
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=username, to=to, message=str(message.encode("utf-8"))))
2016-10-02 20:48:14 +00:00
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(username))
return 404
except exceptions.channelModeratedException:
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(username, to))
return 403
except exceptions.channelNoPermissionsException:
log.warning("{} tried to send a message to channel {}, but they have no write permissions".format(username, to))
return 404
except exceptions.userRestrictedException:
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(username, to))
return 404
except exceptions.userNotFoundException:
log.warning("User not connected to IRC/Bancho")
return 401
""" 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:
return username
# Exact match first
result = glob.db.fetch("SELECT id FROM users WHERE username = %s LIMIT 1", [username])
if result is not None:
return username
# Username not found, replace _ with space
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:
"""
2016-10-02 20:48:14 +00:00
userID = userUtils.getID(username)
2016-09-02 15:45:10 +00:00
if not userID:
log.warning("{} doesn't exist".format(username))
return
glob.tokens.deleteOldTokens(userID)
glob.tokens.addToken(userID, irc=True)
glob.streams.broadcast("main", serverPackets.userPanel(userID))
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)
2016-09-02 15:45:10 +00:00
if token is None:
log.warning("{} doesn't exist".format(username))
return
logoutEvent.handle(token)
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
"""
2016-10-02 20:48:14 +00:00
userID = userUtils.getID(username)
2016-09-02 15:45:10 +00:00
if not userID:
log.warning("{} doesn't exist".format(username))
return
# NOTE: This should have also `toIRC` = False` tho,
# since we send JOIN message later on ircserver.py.
# Will test this later
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
"""
2016-10-02 20:48:14 +00:00
userID = userUtils.getID(username)
2016-09-02 15:45:10 +00:00
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