pep.py/helpers/chatHelper.py

433 lines
15 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, force=False):
"""
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
:param force: whether to allow game clients to join #spect_ and #multi_ channels
:return: 0 if joined or other IRC code in case of error. Needed only on IRC-side
"""
try:
# 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
# Normal channel, do check stuff
# Make sure the channel exists
if channel not in glob.channels.channels:
2016-12-11 10:07:35 +00:00
raise exceptions.channelUnknownException()
# Make sure a game client is not trying to join a #multi_ or #spect_ channel manually
channelObject = glob.channels.channels[channel]
if channelObject.isSpecial and not token.irc and not force:
raise exceptions.channelUnknownException()
# Add the channel to our joined channel
token.joinChannel(channelObject)
# Send channel joined (IRC)
if glob.irc and not toIRC:
2016-12-11 10:07:35 +00:00
glob.ircServer.banchoJoinChannel(token.username, channel)
# Console output
2016-12-11 10:07:35 +00:00
log.info("{} joined channel {}".format(token.username, channel))
# IRC code return
return 0
except exceptions.channelNoPermissionsException:
2016-12-11 10:07:35 +00:00
log.warning("{} attempted to join channel {}, but they have no read permissions".format(token.username, channel))
return 403
except exceptions.channelUnknownException:
2016-12-11 10:07:35 +00:00
log.warning("{} attempted to join an unknown channel ({})".format(token.username, channel))
return 403
except exceptions.userAlreadyInChannelException:
log.warning("User {} already in channel {}".format(token.username, channel))
return 403
except exceptions.userNotFoundException:
log.warning("User not connected to IRC/Bancho")
return 403 # idk
def partChannel(userID = 0, channel = "", token = None, toIRC = True, kick = False, force=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
:param force: whether to allow game clients to part #spect_ and #multi_ channels
:return: 0 if joined or other IRC code in case of error. Needed only on IRC-side
"""
try:
2016-12-11 10:07:35 +00:00
# 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
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:
2018-03-23 20:27:38 +00:00
raise exceptions.userNotFoundException()
else:
token = token
# 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:
2016-12-11 10:07:35 +00:00
raise exceptions.channelUnknownException()
# Make sure a game client is not trying to join a #multi_ or #spect_ channel manually
channelObject = glob.channels.channels[channel]
if channelObject.isSpecial and not token.irc and not force:
raise exceptions.channelUnknownException()
2016-12-11 10:07:35 +00:00
# Make sure the user is in the channel
if channel not in token.joinedChannels:
raise exceptions.userNotInChannelException()
# Part channel (token-side and channel-side)
2016-12-11 10:07:35 +00:00
token.partChannel(channelObject)
# Delete temporary channel if everyone left
if "chat/{}".format(channelObject.name) in glob.streams.streams:
if channelObject.temp and len(glob.streams.streams["chat/{}".format(channelObject.name)].clients) - 1 == 0:
2016-12-11 10:07:35 +00:00
glob.channels.removeChannel(channelObject.name)
# 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 and toIRC:
2016-12-11 10:07:35 +00:00
glob.ircServer.banchoPartChannel(token.username, channel)
# Console output
2016-12-11 10:07:35 +00:00
log.info("{} parted channel {} ({})".format(token.username, channel, channelClient))
# Return IRC code
return 0
except exceptions.channelUnknownException:
2016-12-11 10:07:35 +00:00
log.warning("{} attempted to part an unknown channel ({})".format(token.username, channel))
return 403
2016-12-11 10:07:35 +00:00
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
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:
2016-12-11 10:07:35 +00:00
#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:
2018-03-23 20:27:38 +00:00
raise exceptions.userNotFoundException()
else:
# token object alredy passed, get its string and its username (fro)
fro = token.username
2016-12-11 10:07:35 +00:00
#tokenString = token.token
# 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()
2016-12-11 22:12:06 +00:00
# Redirect !report to FokaBot
if message.startswith("!report"):
to = glob.BOT_NAME
2016-12-11 22:12:06 +00:00
# Determine internal name if needed
# (toclient is used clientwise for #multiplayer and #spectator channels)
toClient = to
if to == "#spectator":
if token.spectating is None:
2016-12-11 10:07:35 +00:00
s = token.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"
2018-03-23 20:27:38 +00:00
# Make sure the message is valid
if not message.strip():
raise exceptions.invalidArgumentsException()
# Truncate message if > 2048 characters
message = message[:2048]+"..." if len(message) > 2048 else message
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
2016-12-11 10:07:35 +00:00
packet = serverPackets.sendMessage(token.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:
2016-12-11 10:07:35 +00:00
raise exceptions.channelUnknownException()
# Make sure the channel is not in moderated mode
if glob.channels.channels[to].moderated and not token.admin:
2016-12-11 10:07:35 +00:00
raise exceptions.channelModeratedException()
# Make sure we are in the channel
if to not in token.joinedChannels:
# I'm too lazy to put and test the correct IRC error code here...
# but IRC is not strict at all so who cares
raise exceptions.channelNoPermissionsException()
# Make sure we have write permissions
if not glob.channels.channels[to].publicWrite and not token.admin:
2016-12-11 10:07:35 +00:00
raise exceptions.channelNoPermissionsException()
2016-12-11 22:12:06 +00:00
# Add message in buffer
token.addMessageInBuffer(to, message)
# Everything seems fine, build recipients list and send packet
2016-12-11 10:07:35 +00:00
glob.streams.broadcast("chat/{}".format(to), packet, but=[token.token])
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:
2016-12-11 10:07:35 +00:00
raise exceptions.userNotFoundException()
# Make sure the recipient is not a tournament client
2016-12-11 10:07:35 +00:00
#if recipientToken.tournament:
# raise exceptions.userTournamentException()
# Make sure the recipient is not restricted or we are FokaBot
2018-09-01 23:45:33 +00:00
if recipientToken.restricted and fro.lower() != glob.BOT_NAME.lower():
raise exceptions.userRestrictedException()
# TODO: Make sure the recipient has not disabled PMs for non-friends or he's our friend
# Away check
2016-12-11 10:07:35 +00:00
if recipientToken.awayCheck(token.userID):
2016-12-28 15:15:07 +00:00
sendMessage(to, fro, "\x01ACTION is away: {}\x01".format(recipientToken.awayMessage))
# Check message templates (mods/admins only)
if message in messageTemplates.templates and token.admin:
sendMessage(fro, to, messageTemplates.templates[message])
# Everything seems fine, send packet
recipientToken.enqueue(packet)
# Send the message to IRC
if glob.irc and toIRC:
messageSplitInLines = message.encode("latin-1").decode("utf-8").split("\n")
for line in messageSplitInLines:
if line == messageSplitInLines[:1] and line == "":
continue
glob.ircServer.banchoMessage(fro, to, line)
# Spam protection (ignore FokaBot)
2016-12-11 10:07:35 +00:00
if token.userID > 999:
token.spamProtection()
# Fokabot message
2018-09-01 23:45:33 +00:00
if isChannel or to.lower() == glob.BOT_NAME.lower():
2016-12-11 10:07:35 +00:00
fokaMessage = fokabot.fokabotResponse(token.username, to, message)
2016-09-02 15:45:10 +00:00
if fokaMessage:
sendMessage(glob.BOT_NAME, to if isChannel else fro, fokaMessage)
# File and discord logs (public chat only)
if to.startswith("#") and not (message.startswith("\x01ACTION is playing") and to.startswith("#spect_")):
log.chat("{fro} @ {to}: {message}".format(fro=token.username, to=to, message=message.encode("latin-1").decode("utf-8")))
glob.schiavo.sendChatlog("**{fro} @ {to}:** {message}".format(fro=token.username, to=to, message=message.encode("latin-1").decode("utf-8")))
return 0
except exceptions.userSilencedException:
token.enqueue(serverPackets.silenceEndTime(token.getSilenceSecondsLeft()))
2016-12-11 10:07:35 +00:00
log.warning("{} tried to send a message during silence".format(token.username))
return 404
except exceptions.channelModeratedException:
2016-12-11 10:07:35 +00:00
log.warning("{} tried to send a message to a channel that is in moderated mode ({})".format(token.username, to))
return 404
except exceptions.channelUnknownException:
2016-12-11 10:07:35 +00:00
log.warning("{} tried to send a message to an unknown channel ({})".format(token.username, to))
return 403
except exceptions.channelNoPermissionsException:
2016-12-11 10:07:35 +00:00
log.warning("{} tried to send a message to channel {}, but they have no write permissions".format(token.username, to))
return 404
except exceptions.userRestrictedException:
2016-12-11 10:07:35 +00:00
log.warning("{} tried to send a message {}, but the recipient is in restricted mode".format(token.username, to))
return 404
except exceptions.userTournamentException:
2016-12-11 10:07:35 +00:00
log.warning("{} tried to send a message {}, but the recipient is a tournament client".format(token.username, to))
return 404
except exceptions.userNotFoundException:
log.warning("User not connected to IRC/Bancho")
return 401
2018-03-23 20:27:38 +00:00
except exceptions.invalidArgumentsException:
log.warning("{} tried to send an invalid message to {}".format(token.username, to))
return 404
""" IRC-Bancho Connect/Disconnect/Join/Part interfaces"""
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