2016-10-02 20:48:14 +00:00
|
|
|
from common.log import logUtils as log
|
|
|
|
from common.ripple import userUtils
|
2016-07-14 10:37:07 +00:00
|
|
|
from constants import exceptions
|
2016-10-02 20:48:14 +00:00
|
|
|
from constants import messageTemplates
|
2016-07-14 10:37:07 +00:00
|
|
|
from constants import serverPackets
|
|
|
|
from events import logoutEvent
|
2016-10-02 20:48:14 +00:00
|
|
|
from objects import fokabot
|
|
|
|
from objects import glob
|
|
|
|
|
2016-07-14 10:37:07 +00:00
|
|
|
|
|
|
|
def joinChannel(userID = 0, channel = "", token = None, toIRC = True):
|
|
|
|
"""
|
|
|
|
Join a channel
|
|
|
|
|
2016-11-17 18:13:06 +00:00
|
|
|
: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
|
2016-07-14 10:37:07 +00:00
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# Get token if not defined
|
2016-09-02 15:45:10 +00:00
|
|
|
if token is None:
|
2016-07-14 10:37:07 +00:00
|
|
|
token = glob.tokens.getTokenFromUserID(userID)
|
|
|
|
# Make sure the token exists
|
2016-09-02 15:45:10 +00:00
|
|
|
if token is None:
|
2016-07-14 10:37:07 +00:00
|
|
|
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
|
|
|
|
|
2016-11-17 18:13:06 +00:00
|
|
|
: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
|
2016-07-14 10:37:07 +00:00
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# Get token if not defined
|
2016-09-02 15:45:10 +00:00
|
|
|
if token is None:
|
2016-07-14 10:37:07 +00:00
|
|
|
token = glob.tokens.getTokenFromUserID(userID)
|
|
|
|
# Make sure the token exists
|
2016-09-02 15:45:10 +00:00
|
|
|
if token is None:
|
2016-07-14 10:37:07 +00:00
|
|
|
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":
|
2016-10-04 20:10:07 +00:00
|
|
|
if token.spectating is None:
|
2016-07-14 10:37:07 +00:00
|
|
|
s = userID
|
|
|
|
else:
|
2016-10-07 12:13:42 +00:00
|
|
|
s = token.spectatingUserID
|
2016-07-14 10:37:07 +00:00
|
|
|
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:
|
2016-08-10 10:24:41 +00:00
|
|
|
token.enqueue(serverPackets.channelKicked(channelClient))
|
2016-07-14 10:37:07 +00:00
|
|
|
|
|
|
|
# IRC part
|
|
|
|
if glob.irc == True and toIRC == True:
|
|
|
|
glob.ircServer.banchoPartChannel(username, channel)
|
|
|
|
|
|
|
|
# Console output
|
2016-08-10 10:24:41 +00:00
|
|
|
log.info("{} parted channel {} ({})".format(username, channel, channelClient))
|
2016-07-14 10:37:07 +00:00
|
|
|
|
|
|
|
# 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
|
|
|
|
|
2016-11-17 18:13:06 +00:00
|
|
|
: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
|
2016-07-14 10:37:07 +00:00
|
|
|
"""
|
|
|
|
try:
|
|
|
|
tokenString = ""
|
|
|
|
# Get token object if not passed
|
2016-09-02 15:45:10 +00:00
|
|
|
if token is None:
|
2016-07-14 10:37:07 +00:00
|
|
|
token = glob.tokens.getTokenFromUsername(fro)
|
2016-09-02 15:45:10 +00:00
|
|
|
if token is None:
|
2016-07-14 10:37:07 +00:00
|
|
|
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
|
|
|
|
|
2016-10-05 21:28:26 +00:00
|
|
|
# Make sure this is not a tournament client
|
|
|
|
if token.tournament:
|
|
|
|
raise exceptions.userTournamentException()
|
|
|
|
|
2016-07-14 10:37:07 +00:00
|
|
|
# Make sure the user is not in restricted mode
|
2016-09-02 15:45:10 +00:00
|
|
|
if token.restricted:
|
2016-10-05 21:28:26 +00:00
|
|
|
raise exceptions.userRestrictedException()
|
2016-07-14 10:37:07 +00:00
|
|
|
|
|
|
|
# Make sure the user is not silenced
|
2016-09-02 15:45:10 +00:00
|
|
|
if token.isSilenced():
|
2016-10-05 21:28:26 +00:00
|
|
|
raise exceptions.userSilencedException()
|
2016-07-14 10:37:07 +00:00
|
|
|
|
|
|
|
# Determine internal name if needed
|
|
|
|
# (toclient is used clientwise for #multiplayer and #spectator channels)
|
|
|
|
toClient = to
|
|
|
|
if to == "#spectator":
|
2016-10-04 20:10:07 +00:00
|
|
|
if token.spectating is None:
|
2016-07-14 10:37:07 +00:00
|
|
|
s = userID
|
|
|
|
else:
|
2016-10-07 12:13:42 +00:00
|
|
|
s = token.spectatingUserID
|
2016-07-14 10:37:07 +00:00
|
|
|
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
|
|
|
|
2016-07-14 10:37:07 +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:
|
2016-07-14 10:37:07 +00:00
|
|
|
# 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
|
2016-11-17 18:13:06 +00:00
|
|
|
recipients = glob.channels.channels[to].connectedUsers[:]
|
2016-07-14 10:37:07 +00:00
|
|
|
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:
|
2016-07-14 10:37:07 +00:00
|
|
|
raise exceptions.userNotFoundException
|
|
|
|
|
2016-10-05 21:28:26 +00:00
|
|
|
# Make sure the recipient is not a tournament client
|
|
|
|
if recipientToken.tournament:
|
|
|
|
raise exceptions.userTournamentException()
|
|
|
|
|
2016-07-14 10:37:07 +00:00
|
|
|
# Make sure the recipient is not restricted or we are FokaBot
|
|
|
|
if recipientToken.restricted == True and fro.lower() != "fokabot":
|
2016-10-05 21:28:26 +00:00
|
|
|
raise exceptions.userRestrictedException()
|
2016-07-14 10:37:07 +00:00
|
|
|
|
|
|
|
# TODO: Make sure the recipient has not disabled PMs for non-friends or he's our friend
|
|
|
|
|
2016-11-13 11:23:45 +00:00
|
|
|
# Away check
|
|
|
|
if recipientToken.awayCheck(userID):
|
2016-11-13 11:35:15 +00:00
|
|
|
sendMessage(to, fro, "\x01ACTION is away: {message}\x01".format(code=chr(int(1)), message=recipientToken.awayMessage))
|
2016-11-13 11:23:45 +00:00
|
|
|
|
2016-07-14 10:37:07 +00:00
|
|
|
# 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:
|
2016-07-14 10:37:07 +00:00
|
|
|
sendMessage("FokaBot", to if isChannel else fro, fokaMessage)
|
|
|
|
|
|
|
|
# File and discord logs (public chat only)
|
2016-11-13 11:35:15 +00:00
|
|
|
if to.startswith("#") and not (message.startswith("\x01ACTION is playing") and to.startswith("#spect_")):
|
2016-07-14 10:37:07 +00:00
|
|
|
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]))
|
2016-07-14 10:37:07 +00:00
|
|
|
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
|
2016-10-05 21:28:26 +00:00
|
|
|
except exceptions.userTournamentException:
|
|
|
|
log.warning("{} tried to send a message {}, but the recipient is a tournament client".format(username, to))
|
|
|
|
return 404
|
2016-07-14 10:37:07 +00:00
|
|
|
except exceptions.userNotFoundException:
|
|
|
|
log.warning("User not connected to IRC/Bancho")
|
|
|
|
return 401
|
|
|
|
|
|
|
|
|
|
|
|
""" IRC-Bancho Connect/Disconnect/Join/Part interfaces"""
|
2016-09-02 10:41:19 +00:00
|
|
|
def fixUsernameForBancho(username):
|
2016-11-17 18:13:06 +00:00
|
|
|
"""
|
|
|
|
Convert username from IRC format (without spaces) to Bancho format (with spaces)
|
|
|
|
|
|
|
|
:param username: username to convert
|
|
|
|
:return: converted username
|
|
|
|
"""
|
2016-09-16 16:20:32 +00:00
|
|
|
# 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
|
2016-09-02 10:41:19 +00:00
|
|
|
return username.replace("_", " ")
|
|
|
|
|
|
|
|
def fixUsernameForIRC(username):
|
2016-11-17 18:13:06 +00:00
|
|
|
"""
|
|
|
|
Convert an username from Bancho format to IRC format (underscores instead of spaces)
|
|
|
|
|
|
|
|
:param username: username to convert
|
|
|
|
:return: converted username
|
|
|
|
"""
|
2016-09-02 10:41:19 +00:00
|
|
|
return username.replace(" ", "_")
|
|
|
|
|
2016-07-14 10:37:07 +00:00
|
|
|
def IRCConnect(username):
|
2016-11-17 18:13:06 +00:00
|
|
|
"""
|
|
|
|
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:
|
2016-07-14 10:37:07 +00:00
|
|
|
log.warning("{} doesn't exist".format(username))
|
|
|
|
return
|
|
|
|
glob.tokens.deleteOldTokens(userID)
|
|
|
|
glob.tokens.addToken(userID, irc=True)
|
2016-10-01 19:19:03 +00:00
|
|
|
glob.streams.broadcast("main", serverPackets.userPanel(userID))
|
2016-07-14 10:37:07 +00:00
|
|
|
log.info("{} logged in from IRC".format(username))
|
|
|
|
|
|
|
|
def IRCDisconnect(username):
|
2016-11-17 18:13:06 +00:00
|
|
|
"""
|
|
|
|
Handle IRC logout bancho-side.
|
|
|
|
Remove token and broadcast logout packet.
|
|
|
|
|
|
|
|
:param username: username
|
|
|
|
:return:
|
|
|
|
"""
|
2016-07-14 10:37:07 +00:00
|
|
|
token = glob.tokens.getTokenFromUsername(username)
|
2016-09-02 15:45:10 +00:00
|
|
|
if token is None:
|
2016-07-14 10:37:07 +00:00
|
|
|
log.warning("{} doesn't exist".format(username))
|
|
|
|
return
|
|
|
|
logoutEvent.handle(token)
|
|
|
|
log.info("{} disconnected from IRC".format(username))
|
|
|
|
|
|
|
|
def IRCJoinChannel(username, channel):
|
2016-11-17 18:13:06 +00:00
|
|
|
"""
|
|
|
|
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:
|
2016-07-14 10:37:07 +00:00
|
|
|
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):
|
2016-11-17 18:13:06 +00:00
|
|
|
"""
|
|
|
|
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:
|
2016-07-14 10:37:07 +00:00
|
|
|
log.warning("{} doesn't exist".format(username))
|
|
|
|
return
|
2016-11-13 11:23:45 +00:00
|
|
|
return partChannel(userID, channel)
|
|
|
|
|
|
|
|
def IRCAway(username, message):
|
2016-11-17 18:13:06 +00:00
|
|
|
"""
|
|
|
|
Handle IRC away command bancho-side.
|
|
|
|
|
|
|
|
:param username:
|
|
|
|
:param message: away message
|
|
|
|
:return: IRC return code
|
|
|
|
"""
|
2016-11-13 11:23:45 +00:00
|
|
|
userID = userUtils.getID(username)
|
|
|
|
if not userID:
|
|
|
|
log.warning("{} doesn't exist".format(username))
|
|
|
|
return
|
2016-11-17 18:13:06 +00:00
|
|
|
glob.tokens.getTokenFromUserID(userID).awayMessage = message
|
2016-11-13 11:23:45 +00:00
|
|
|
return 305 if message == "" else 306
|