.BANCHO. Add IRC support, internal changes

This commit is contained in:
Nyo 2016-07-14 12:37:07 +02:00
parent 34be190aa3
commit 95df629e1c
32 changed files with 1191 additions and 351 deletions

View File

@ -77,3 +77,6 @@ class need2FAException(Exception):
class userRestrictedException(Exception):
pass
class haxException(Exception):
pass

View File

@ -127,7 +127,7 @@ def kick(fro, chan, message):
def fokabotReconnect(fro, chan, message):
# Check if fokabot is already connected
if glob.tokens.getTokenFromUserID(999) != None:
return"Fokabot is already connected to Bancho"
return "Fokabot is already connected to Bancho"
# Fokabot is not connected, connect it
fokabot.connect()

View File

@ -130,16 +130,9 @@ def userStats(userID, force = False):
# Get userID's token from tokens list
userToken = glob.tokens.getTokenFromUserID(userID)
if userToken == None:
return bytes() # NOTE: ???
if userToken.restricted == True and force == False:
return bytes()
# Stats are cached in token object
#rankedScore = userHelper.getRankedScore(userID, userToken.gameMode)
#accuracy = userHelper.getAccuracy(userID, userToken.gameMode)/100
#playcount = userHelper.getPlaycount(userID, userToken.gameMode)
#totalScore = userHelper.getTotalScore(userID, userToken.gameMode)
#gameRank = userHelper.getGameRank(userID, userToken.gameMode)
#pp = int(userHelper.getPP(userID, userToken.gameMode))
if (userToken.restricted == True or userToken.irc == True) and force == False:
return bytes()
return packetHelper.buildPacket(packetIDs.server_userStats,
[
[userID, dataTypes.uInt32],

View File

@ -4,6 +4,7 @@ from constants import serverPackets
from helpers import userHelper
from helpers import logHelper as log
from constants import actions
from helpers import chatHelper as chat
def handle(userToken, packetData):
# Get usertoken data
@ -58,7 +59,7 @@ def handle(userToken, packetData):
# NOTE: Remove this when osu!direct will be fixed
if userToken.actionID == actions.osuDirect and userToken.osuDirectAlert == False:
userToken.osuDirectAlert = True
userToken.enqueue(serverPackets.sendMessage("FokaBot", userToken.username, "Sup! osu!direct works, but you'll need to update the switcher to have the Download button working. If you didn't update the switcher yet, please do!"))
chat.sendMessage("FokaBot", userToken.username, "Sup! osu!direct works, but you'll need to update the switcher to have the Download button working. If you didn't update the switcher yet, please do!")
# Console output

View File

@ -1,56 +1,7 @@
"""
Event called when someone joins a channel
"""
from constants import clientPackets
from helpers import consoleHelper
from constants import bcolors
from constants import serverPackets
from objects import glob
from constants import exceptions
from helpers import logHelper as log
from helpers import chatHelper as chat
def handle(userToken, packetData):
# Channel join packet
packetData = clientPackets.channelJoin(packetData)
joinChannel(userToken, packetData["channel"])
def joinChannel(userToken, channelName):
'''
Join a channel
userToken -- user token object of user that joins the chanlle
channelName -- name of channel
'''
try:
# Get usertoken data
username = userToken.username
userID = userToken.userID
# Check spectator channel
# If it's spectator channel, skip checks and list stuff
if channelName != "#spectator" and channelName != "#multiplayer":
# Normal channel, do check stuff
# Make sure the channel exists
if channelName not in glob.channels.channels:
raise exceptions.channelUnknownException
# Check channel permissions
if glob.channels.channels[channelName].publicRead == False and userToken.admin == False:
raise exceptions.channelNoPermissionsException
# Add our userID to users in that channel
glob.channels.channels[channelName].userJoin(userID)
# Add the channel to our joined channel
userToken.joinChannel(channelName)
# Send channel joined
userToken.enqueue(serverPackets.channelJoinSuccess(userID, channelName))
# Console output
log.info("{} joined channel {}".format(username, channelName))
except exceptions.channelNoPermissionsException:
log.warning("{} attempted to join channel {}, but they have no read permissions".format(username, channelName))
except exceptions.channelUnknownException:
log.warning("{} attempted to join an unknown channel ({})".format(username, channelName))
chat.joinChannel(token=userToken, channel=packetData["channel"])

View File

@ -8,13 +8,14 @@ from objects import glob
from constants import clientPackets
from constants import serverPackets
from helpers import logHelper as log
from helpers import chatHelper as chat
def handle(userToken, packetData):
# Channel part packet
# Channel join packet
packetData = clientPackets.channelPart(packetData)
partChannel(userToken, packetData["channel"])
chat.partChannel(token=userToken, channel=packetData["channel"])
def partChannel(userToken, channelName, kick = False):
"""def partChannel(userToken, channelName, kick = False):
# Get usertoken data
username = userToken.username
userID = userToken.userID
@ -34,4 +35,4 @@ def partChannel(userToken, channelName, kick = False):
userToken.enqueue(serverPackets.channelKicked(channelName))
# Console output
log.info("{} parted channel {}".format(username, channelName))
log.info("{} parted channel {}".format(username, channelName))"""

View File

@ -3,6 +3,7 @@ from constants import serverPackets
from objects import glob
from constants import exceptions
from helpers import logHelper as log
from helpers import chatHelper as chat
def handle(userToken, packetData):
# read packet data
@ -44,8 +45,7 @@ def joinMatch(userToken, matchID, password):
# Send packets
userToken.enqueue(serverPackets.matchJoinSuccess(matchID))
userToken.enqueue(serverPackets.channelJoinSuccess(userID, "#multiplayer"))
#userToken.enqueue(serverPackets.sendMessage("FokaBot", "#multiplayer", "Hi {}, and welcome to Ripple's multiplayer mode! This feature is still WIP and might have some issues. If you find any bugs, please report them (by clicking here)[https://ripple.moe/index.php?p=22].".format(username)))
chat.joinChannel(token=userToken, channel="#multi_{}".format(matchID))
except exceptions.matchNotFoundException:
userToken.enqueue(serverPackets.matchJoinFail())
log.warning("{} has tried to join a mp room, but it doesn't exist".format(userToken.username))

View File

@ -8,12 +8,12 @@ from helpers import locationHelper
from helpers import countryHelper
import time
from helpers import generalFunctions
from events import channelJoinEvent
import sys
import traceback
from helpers import requestHelper
from helpers import discordBotHelper
from helpers import logHelper as log
from helpers import chatHelper as chat
def handle(tornadoRequest):
# Data to return
@ -30,6 +30,10 @@ def handle(tornadoRequest):
# If true, print error to console
err = False
# Make sure loginData is valid
if len(loginData) < 3:
raise exceptions.haxException()
# Try to get the ID from username
username = str(loginData[0])
userID = userHelper.getID(username)
@ -59,6 +63,9 @@ def handle(tornadoRequest):
responseToken = glob.tokens.addToken(userID, requestIP)
responseTokenString = responseToken.token
# Check restricted mode (and eventually send message)
responseToken.checkRestricted()
# Set silence end UNIX time in token
responseToken.silenceEndTime = userHelper.getSilenceEnd(userID)
@ -101,12 +108,12 @@ def handle(tornadoRequest):
responseToken.enqueue(serverPackets.channelInfoEnd())
# Default opened channels
# TODO: Configurable default channels
channelJoinEvent.joinChannel(responseToken, "#osu")
channelJoinEvent.joinChannel(responseToken, "#announce")
chat.joinChannel(token=responseToken, channel="#osu")
chat.joinChannel(token=responseToken, channel="#announce")
# Join admin channel if we are an admin
if responseToken.admin == True:
channelJoinEvent.joinChannel(responseToken, "#admin")
chat.joinChannel(token=responseToken, channel="#admin")
# Output channels info
for key, value in glob.channels.channels.items():
@ -156,6 +163,12 @@ def handle(tornadoRequest):
# (we don't use enqueue because we don't have a token since login has failed)
err = True
responseData += serverPackets.loginFailed()
except exceptions.haxException:
# 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
@ -172,9 +185,14 @@ def handle(tornadoRequest):
except exceptions.need2FAException:
# User tried to log in from unknown IP
responseData += serverPackets.needVerification()
except:
log.error("Unknown error!\n```\n{}\n{}```".format(sys.exc_info(), traceback.format_exc()))
finally:
# Console and discord log
msg = "Bancho login request from {} for user {} ({})".format(requestIP, loginData[0], "failed" if err == True else "success")
if len(loginData) < 3:
msg = "Invalid bancho login request from **{}** (insufficient POST data)".format(requestIP)
else:
msg = "Bancho login request from **{}** for user **{}** ({}) **({})**".format(requestIP, loginData[0], loginData[2], "failed" if err == True else "success")
log.info(msg, True)
# Return token string and data

View File

@ -4,8 +4,9 @@ from constants import bcolors
from constants import serverPackets
import time
from helpers import logHelper as log
from helpers import chatHelper as chat
def handle(userToken, _):
def handle(userToken, _=None):
# get usertoken data
userID = userToken.userID
username = userToken.username
@ -15,8 +16,9 @@ def handle(userToken, _):
# 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:
if int(time.time()-userToken.loginTime) >= 5 or userToken.irc == True:
# Stop spectating if needed
# TODO: Call stopSpectatingEvent!!!!!!!!!
if userToken.spectating != 0:
# The user was spectating someone
spectatorHostToken = glob.tokens.getTokenFromUserID(userToken.spectating)
@ -26,13 +28,17 @@ def handle(userToken, _):
# Part all joined channels
for i in userToken.joinedChannels:
glob.channels.channels[i].userPart(userID)
chat.partChannel(token=userToken, channel=i)
# TODO: Lobby left if joined
# Enqueue our disconnection to everyone else
glob.tokens.enqueueAll(serverPackets.userLogout(userID))
# Disconnect from IRC if needed
if userToken.irc == True and glob.irc == True:
glob.ircServer.forceDisconnection(userToken.username)
# Delete token
glob.tokens.deleteToken(requestToken)

View File

@ -1,6 +1,7 @@
from objects import glob
from events import channelPartEvent
from helpers import logHelper as log
from helpers import chatHelper as chat
def handle(userToken, _):
# Get usertoken data
@ -11,7 +12,7 @@ def handle(userToken, _):
glob.matches.lobbyUserPart(userID)
# Part lobby channel
channelPartEvent.partChannel(userToken, "#lobby", True)
chat.partChannel(channel="#lobby", token=userToken, kick=True)
# Console output
log.info("{} has left multiplayer lobby".format(username))

View File

@ -1,4 +1,5 @@
from objects import glob
from helpers import chatHelper as chat
from constants import serverPackets
def handle(userToken, _):
@ -22,6 +23,8 @@ def handle(userToken, _):
# Set slot to free
match.userLeft(userID)
# Part #multiplayer channel
chat.partChannel(token=userToken, channel="#multi_{}".format(matchID))
# Set usertoken match to -1
userToken.partMatch()
userToken.enqueue(serverPackets.channelKicked("#multiplayer"))

View File

@ -1,79 +1,7 @@
from helpers import consoleHelper
from constants import bcolors
from constants import clientPackets
from constants import serverPackets
from objects import glob
from objects import fokabot
from constants import exceptions
from constants import messageTemplates
from helpers import generalFunctions
from helpers import userHelper
from helpers import logHelper as log
import time
from helpers import chatHelper as chat
def handle(userToken, packetData):
"""
Event called when someone sends a private message
userToken -- request user token
packetData -- request data bytes
"""
try:
# Get usertoken username
username = userToken.username
userID = userToken.userID
# Make sure the user is not in restricted mode
if userToken.restricted == True:
raise exceptions.userRestrictedException
# Private message packet
packetData = clientPackets.sendPrivateMessage(packetData)
# Make sure the user is not silenced
if userToken.isSilenced() == True:
raise exceptions.userSilencedException
# Check message length
packetData["message"] = packetData["message"][:2048]+"..." if len(packetData["message"]) > 2048 else packetData["message"]
if packetData["to"] == "FokaBot":
# FokaBot command check
fokaMessage = fokabot.fokabotResponse(username, packetData["to"], packetData["message"])
if fokaMessage != False:
userToken.enqueue(serverPackets.sendMessage("FokaBot", username, fokaMessage))
log.pm("FokaBot -> {}: {}".format(packetData["to"], str(fokaMessage.encode("UTF-8"))))
else:
# Send packet message to target if it exists
token = glob.tokens.getTokenFromUsername(packetData["to"])
if token == None:
raise exceptions.tokenNotFoundException()
# Check message templates (mods/admins only)
if packetData["message"] in messageTemplates.templates and userToken.admin == True:
packetData["message"] = messageTemplates.templates[packetData["message"]]
# Send message to target
token.enqueue(serverPackets.sendMessage(username, packetData["to"], packetData["message"]))
# Send away message to sender if needed
if token.awayMessage != "":
userToken.enqueue(serverPackets.sendMessage(packetData["to"], username, "This user is away: {}".format(token.awayMessage)))
# Spam protection
userToken.spamProtection()
# Console and file output
log.pm("{} -> {}: {}".format(username, packetData["to"], packetData["message"]))
except exceptions.userSilencedException:
userToken.enqueue(serverPackets.silenceEndTime(userToken.getSilenceSecondsLeft()))
log.warning("{} tried to send a message during silence".format(username))
except exceptions.tokenNotFoundException:
# Token not found, user disconnected
log.warning("{} tried to send a message to {}, but their token couldn't be found".format(username, packetData["to"]))
except exceptions.messageTooLongException:
# Message > 256 silence
userToken.silence(2*3600, "Sending messages longer than 256 characters")
except exceptions.userRestrictedException:
pass
# Send private message packet
packetData = clientPackets.sendPrivateMessage(packetData)
chat.sendMessage(token=userToken, to=packetData["to"], message=packetData["message"])

View File

@ -1,135 +1,7 @@
from constants import exceptions
from constants import clientPackets
from objects import glob
from objects import fokabot
from constants import serverPackets
from helpers import discordBotHelper
from helpers import logHelper as log
from helpers import userHelper
import time
from helpers import chatHelper as chat
def handle(userToken, packetData):
"""
Event called when someone sends a public message
userToken -- request user token
packetData -- request data bytes
"""
try:
# Get userToken data
userID = userToken.userID
username = userToken.username
# Make sure the user is not in restricted mode
if userToken.restricted == True:
raise exceptions.userRestrictedException
# Public chat packet
packetData = clientPackets.sendPublicMessage(packetData)
# Receivers
who = []
# Make sure the user is not silenced
if userToken.isSilenced() == True:
raise exceptions.userSilencedException
# Check message length
packetData["message"] = packetData["message"][:2048]+"..." if len(packetData["message"]) > 2048 else packetData["message"]
# Get receivers list
# Check #spectator
if packetData["to"] == "#spectator":
# Spectator channel
# Send this packet to every spectator and host
if userToken.spectating == 0:
# We have sent to send a message to our #spectator channel
targetToken = userToken
who = targetToken.spectators[:]
# No need to remove us because we are the host so we are not in spectators list
else:
# We have sent a message to someone else's #spectator
targetToken = glob.tokens.getTokenFromUserID(userToken.spectating)
who = targetToken.spectators[:]
# Remove us
if userID in who:
who.remove(userID)
# Add host
who.append(targetToken.userID)
elif packetData["to"] == "#multiplayer":
# Multiplayer Channel
# Get match ID and match object
matchID = userToken.matchID
# Make sure we are in a match
if matchID == -1:
return
# Make sure the match exists
if matchID not in glob.matches.matches:
return
# The match exists, get object
match = glob.matches.matches[matchID]
# Create targets list
who = []
for i in range(0,16):
uid = match.slots[i]["userID"]
if uid > -1 and uid != userID:
who.append(uid)
else:
# Standard channel
# Make sure the channel exists
if packetData["to"] not in glob.channels.channels:
raise exceptions.channelUnknownException
# Make sure the channel is not in moderated mode
if glob.channels.channels[packetData["to"]].moderated == True and userToken.admin == False:
raise exceptions.channelModeratedException
# Make sure we have write permissions
if glob.channels.channels[packetData["to"]].publicWrite == False and userToken.admin == False:
raise exceptions.channelNoPermissionsException
# Send this packet to everyone in that channel except us
who = glob.channels.channels[packetData["to"]].getConnectedUsers()[:]
if userID in who:
who.remove(userID)
# We have receivers
# Send packet to required users
glob.tokens.multipleEnqueue(serverPackets.sendMessage(username, packetData["to"], packetData["message"]), who, False)
# Fokabot command check
fokaMessage = fokabot.fokabotResponse(username, packetData["to"], packetData["message"])
if fokaMessage != False:
who.append(userID)
glob.tokens.multipleEnqueue(serverPackets.sendMessage("FokaBot", packetData["to"], fokaMessage), who, False)
log.chat("FokaBot @ {}: {}".format(packetData["to"], str(fokaMessage.encode("UTF-8"))))
# Spam protection
userToken.spamProtection()
# Console and file log
log.chat("{fro} @ {to}: {message}".format(fro=username, to=packetData["to"], message=str(packetData["message"].encode("utf-8"))))
# Discord log
discordBotHelper.sendChatlog("**{fro} @ {to}:** {message}".format(fro=username, to=packetData["to"], message=str(packetData["message"].encode("utf-8"))[2:-1]))
except exceptions.userSilencedException:
userToken.enqueue(serverPackets.silenceEndTime(userToken.getSilenceSecondsLeft()))
log.warning("{} tried to send a message during silence".format(username))
except exceptions.channelModeratedException:
log.warning("{} tried to send a message to a channel that is in moderated mode ({})".format(username, packetData["to"]))
except exceptions.channelUnknownException:
log.warning("{} tried to send a message to an unknown channel ({})".format(username, packetData["to"]))
except exceptions.channelNoPermissionsException:
log.warning("{} tried to send a message to channel {}, but they have no write permissions".format(username, packetData["to"]))
except exceptions.messageTooLongException:
# Message > 256 silence
userToken.silence(2*3600, "Sending messages longer than 256 characters")
except exceptions.userRestrictedException:
pass
# Send public message packet
packetData = clientPackets.sendPublicMessage(packetData)
chat.sendMessage(token=userToken, to=packetData["to"], message=packetData["message"])

View File

@ -4,6 +4,7 @@ from constants import exceptions
from objects import glob
from helpers import userHelper
from helpers import logHelper as log
from helpers import chatHelper as chat
def handle(userToken, packetData):
try:
@ -34,12 +35,12 @@ def handle(userToken, packetData):
# Send spectator join packet to host
targetToken.enqueue(serverPackets.addSpectator(userID))
# Join #spectator channel
userToken.enqueue(serverPackets.channelJoinSuccess(userID, "#spectator"))
# Create and join #spectator (#spect_userid) channel
glob.channels.addTempChannel("#spect_{}".format(targetToken.userID))
chat.joinChannel(token=userToken, channel="#spect_{}".format(targetToken.userID))
if len(targetToken.spectators) == 1:
# First spectator, send #spectator join to host too
targetToken.enqueue(serverPackets.channelJoinSuccess(userID, "#spectator"))
chat.joinChannel(token=targetToken, channel="#spect_{}".format(targetToken.userID))
# send fellowSpectatorJoined to all spectators
for spec in targetToken.spectators:

View File

@ -2,6 +2,7 @@ from objects import glob
from constants import serverPackets
from constants import exceptions
from helpers import logHelper as log
from helpers import chatHelper as chat
def handle(userToken, _):
try:
@ -16,6 +17,9 @@ def handle(userToken, _):
raise exceptions.tokenNotFoundException
targetToken.removeSpectator(userID)
# Part #spectator channel
chat.partChannel(token=userToken, channel="#spect_{}".format(target))
# Send the spectator left packet to host
targetToken.enqueue(serverPackets.removeSpectator(userID))
for c in targetToken.spectators:
@ -25,8 +29,7 @@ def handle(userToken, _):
#targetToken.enqueue(serverPackets.fellowSpectatorLeft(userID))
# Console output
# TODO: Move messages in stop spectating
log.info("{} are no longer spectating whoever they were spectating".format(username))
log.info("{} are no longer spectating {}".format(username, target))
except exceptions.tokenNotFoundException:
log.warning("Spectator stop: token not found")
finally:

View File

@ -1,5 +1,7 @@
import datetime
import gzip
import time
from helpers import generalFunctions
from helpers import requestHelper
from objects import glob
from helpers import consoleHelper
@ -257,7 +259,7 @@ class handler(SentryMixin, requestHelper.asyncRequestHandler):
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.</pre></body></html>"
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)
#yield tornado.gen.Task(self.captureMessage, "test")
#self.finish()

335
helpers/chatHelper.py Normal file
View File

@ -0,0 +1,335 @@
from objects import glob
from helpers import logHelper as log
from constants import exceptions
from constants import serverPackets
from objects import fokabot
from helpers import discordBotHelper
from helpers import userHelper
from events import logoutEvent
from events import channelJoinEvent
from constants import messageTemplates
def joinChannel(userID = 0, channel = "", token = None, toIRC = True):
"""
Join a channel
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
if token == None:
token = glob.tokens.getTokenFromUserID(userID)
# Make sure the token exists
if token == 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
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:
# Get token if not defined
if token == None:
token = glob.tokens.getTokenFromUserID(userID)
# Make sure the token exists
if token == 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 == 0:
s = userID
else:
s = token.spectating
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
if kick == True:
token.enqueue(serverPackets.channelKicked(channelObject.clientName))
# IRC part
if glob.irc == True and toIRC == True:
glob.ircServer.banchoPartChannel(username, channel)
# Console output
log.info("{} parted channel {}".format(username, channel))
# 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
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 = ""
# Get token object if not passed
if token == None:
token = glob.tokens.getTokenFromUsername(fro)
if token == 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
recipients = []
# Make sure the user is not in restricted mode
if token.restricted == True:
raise exceptions.userRestrictedException
# Make sure the user is not silenced
if token.isSilenced() == True:
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 == 0:
s = userID
else:
s = token.spectating
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
# Build packet bytes
packet = serverPackets.sendMessage(username, toClient, message)
# Send the message
isChannel = to.startswith("#")
if isChannel == True:
# 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].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 == None:
raise exceptions.userNotFoundException
# 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
# 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)
if fokaMessage != False:
sendMessage("FokaBot", to if isChannel else fro, fokaMessage)
# File and discord logs (public chat only)
if to.startswith("#") == True:
log.chat("{fro} @ {to}: {message}".format(fro=username, to=to, message=str(message.encode("utf-8"))))
discordBotHelper.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.userNotFoundException:
log.warning("User not connected to IRC/Bancho")
return 401
""" IRC-Bancho Connect/Disconnect/Join/Part interfaces"""
def IRCConnect(username):
userID = userHelper.getID(username)
if userID == False:
log.warning("{} doesn't exist".format(username))
return
glob.tokens.deleteOldTokens(userID)
glob.tokens.addToken(userID, irc=True)
glob.tokens.enqueueAll(serverPackets.userPanel(userID))
log.info("{} logged in from IRC".format(username))
def IRCDisconnect(username):
token = glob.tokens.getTokenFromUsername(username)
if token == None:
log.warning("{} doesn't exist".format(username))
return
logoutEvent.handle(token)
log.info("{} disconnected from IRC".format(username))
def IRCJoinChannel(username, channel):
userID = userHelper.getID(username)
if userID == False:
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):
userID = userHelper.getID(username)
if userID == False:
log.warning("{} doesn't exist".format(username))
return
return partChannel(userID, channel)

View File

@ -52,7 +52,6 @@ class config:
self.config.get("server","threads")
self.config.get("server","gzip")
self.config.get("server","gziplevel")
self.config.get("server","localize")
self.config.get("server","cikey")
self.config.get("server","cloudflare")
@ -66,6 +65,12 @@ class config:
self.config.get("discord","enable")
self.config.get("discord","boturl")
self.config.get("discord","devgroup")
self.config.get("irc","enable")
self.config.get("irc","port")
self.config.get("localize","enable")
self.config.get("localize","ipapiurl")
return True
except:
return False
@ -91,7 +96,6 @@ class config:
self.config.set("server", "threads", "16")
self.config.set("server", "gzip", "1")
self.config.set("server", "gziplevel", "6")
self.config.set("server", "localize", "1")
self.config.set("server", "cikey", "changeme")
self.config.set("server", "cloudflare", "0")
@ -109,6 +113,14 @@ class config:
self.config.set("discord", "boturl", "")
self.config.set("discord", "devgroup", "")
self.config.add_section("irc")
self.config.set("irc", "enable", "1")
self.config.set("irc", "port", "6667")
self.config.add_section("localize")
self.config.set("localize", "enable", "1")
self.config.set("localize", "ipapiurl", "http://ip.zxq.co")
# Write ini to file and close
self.config.write(f)
f.close()

View File

@ -44,7 +44,7 @@ class db:
self.lastWorker = 0
self.workersNumber = workers
for i in range(0,self.workersNumber):
print("> Spawning MySQL pettirosso meme {}".format(i))
print(".", end="")
self.workers.append(mysqlWorker(i, host, username, password, database))
def getWorker(self):

View File

@ -21,7 +21,7 @@ def hexString(s):
return -- string with hex value
"""
return ":".join("{:02x}".format(ord(c)) for c in s)
return ":".join("{:02x}".format(ord(str(c))) for c in s)
def readableMods(__mods):
"""

View File

@ -1,12 +1,9 @@
import urllib.request
import json
from objects import glob
from helpers import logHelper as log
# API URL
URL = "http://ip.zxq.co/"
def getCountry(ip):
"""
Get country from IP address
@ -17,7 +14,7 @@ def getCountry(ip):
try:
# Try to get country from Pikolo Aul's Go-Sanic ip API
result = json.loads(urllib.request.urlopen("{}/{}".format(URL, ip), timeout=3).read().decode())["country"]
result = json.loads(urllib.request.urlopen("{}/{}".format(glob.conf.config["localize"]["ipapiurl"], ip), timeout=3).read().decode())["country"]
return result.upper()
except:
log.error("Error in get country")
@ -34,7 +31,7 @@ def getLocation(ip):
try:
# Try to get position from Pikolo Aul's Go-Sanic ip API
result = json.loads(urllib.request.urlopen("{}/{}".format(URL, ip), timeout=3).read().decode())["loc"].split(",")
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])]
except:
log.error("Error in get position")

View File

@ -118,7 +118,7 @@ def packData(__data, __dataType):
# Non empty string
data += b"\x0B"
data += uleb128Encode(len(__data))
data += str.encode(__data, "latin_1")
data += str.encode(__data, "latin_1", "ignore")
elif __dataType == dataTypes.uInt16:
packType = "<H"
elif __dataType == dataTypes.sInt16:

0
irc/__init__.py Normal file
View File

627
irc/ircserver.py Normal file
View File

@ -0,0 +1,627 @@
import sys
import socket
import select
import time
import re
import hashlib
from helpers import logHelper as log
from objects import glob
from helpers import chatHelper as chat
class Client:
"""
IRC Client object
"""
__linesep_regexp = re.compile(r"\r?\n")
def __init__(self, server, sock):
"""
Initialize a Client object
server -- server object
sock -- socket connection object
"""
self.__timestamp = time.time()
self.__readbuffer = ""
self.__writebuffer = ""
self.__sentPing = False
self.__handleCommand = self.passHandler
self.server = server
self.socket = sock
(self.ip, self.port) = sock.getpeername()
self.username = ""
self.supposedUsername = ""
self.joinedChannels = []
def messageChannel(self, channel, command, message, includeSelf=False):
line = ":{} {}".format(command, message)
for _, value in self.server.clients.items():
if channel in value.joinedChannels and (value != self or includeSelf):
value.message(line)
def message(self, msg):
"""
Add a message (basic string) to client buffer.
This is the lowest possible level.
msg -- message to add
"""
self.__writebuffer += msg + "\r\n"
def writeBufferSize(self):
"""
Return this client's write buffer size
return -- write buffer size
"""
return len(self.__writebuffer)
def reply(self, msg):
"""
Add an IRC-like message to client buffer.
msg -- message (without IRC stuff)
"""
self.message(":{} {}".format(self.server.host, msg))
def replyCode(self, code, message, nickname="", channel=""):
"""
Add an IRC-like message to client buffer with code
code -- response code
message -- response message
nickname -- receiver nickname
channel -- optional
"""
if nickname == "":
nickname = self.username
if channel != "":
channel = " "+channel
self.reply("{code:03d} {nickname}{channel} :{message}".format(code=code, nickname=nickname, channel=channel, message=message))
def reply403(self, channel):
"""
Add a 403 reply (no such channel) to client buffer.
channel -- meh
"""
self.replyCode(403, "{} :No such channel".format(channel))
def reply461(self, command):
"""
Add a 461 reply (not enough parameters) to client buffer
command -- command that had not enough parameters
"""
self.replyCode(403, "{} :Not enough parameters".format(command))
def disconnect(self, quitmsg = "Client quit", callLogout = True):
"""
Disconnects this client from the IRC server
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))
self.socket.close()
log.info("[IRC] Disconnected connection from {}:{} ({})".format(self.ip, self.port, quitmsg))
# Remove socket from server
self.server.removeClient(self, quitmsg)
# Bancho logout
if callLogout == True:
chat.IRCDisconnect(self.username)
def readSocket(self):
"""Read data coming from this client socket"""
try:
# Try to read incoming data from socket
data = self.socket.recv(2 ** 10)
log.debug("[IRC] [{}:{}] -> {}".format(self.ip, self.port, data))
quitmsg = "EOT"
except socket.error as x:
# Error while reading data, this client will be disconnected
data = ""
quitmsg = x
if data:
# Parse received data if needed
self.__readbuffer += data.decode("latin_1")
self.parseBuffer()
self.__timestamp = time.time()
self.__sentPing = False
else:
# No data, disconnect this socket
self.disconnect(quitmsg)
def parseBuffer(self):
"""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]
lines = lines[:-1]
# Process every line
for line in lines:
if not line:
# Empty line. Ignore.
continue
# Get arguments
x = line.split(" ", 1)
# Command is the first argument, always uppercase
command = x[0].upper()
if len(x) == 1:
# Command only, no arguments
arguments = []
else:
# We have some arguments
# Weird sorcery
if len(x[1]) > 0 and x[1][0] == ":":
arguments = [x[1][1:]]
else:
y = x[1].split(" :", 1)
arguments = y[0].split()
if len(y) == 2:
arguments.append(y[1])
# Handle command with its arguments
self.__handleCommand(command, arguments)
def writeSocket(self):
"""Write buffer to socket"""
try:
sent = self.socket.send(self.__writebuffer.encode())
log.debug("[IRC] [{}:{}] <- {}".format(self.ip, self.port, self.__writebuffer[:sent]))
self.__writebuffer = self.__writebuffer[sent:]
except socket.error as x:
self.disconnect(x)
def checkAlive(self):
"""Check if this client is still connected"""
now = time.time()
if self.__timestamp + 180 < now:
self.disconnect("ping timeout")
return
if not self.__sentPing and self.__timestamp + 90 < now:
if self.__handleCommand == self.mainHandler:
# Registered.
self.message("PING :{}".format(self.server.host))
self.__sentPing = True
else:
# Not registered.
self.disconnect("ping timeout")
def sendLusers(self):
"""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"""
self.replyCode(375, "- {} Message of the day - ".format(self.server.host))
if len(self.server.motd) == 0:
self.replyCode(422, "MOTD File is missing")
else:
for i in self.server.motd:
self.replyCode(372, "- {}".format(i))
self.replyCode(376, "End of MOTD command")
"""""""""
HANDLERS
"""""""""
def dummyHandler(self, command, arguments):
pass
def passHandler(self, command, arguments):
"""PASS command handler"""
if command == "PASS":
if len(arguments) == 0:
self.reply461("PASS")
else:
# IRC token check
m = hashlib.md5()
m.update(arguments[0].encode("utf-8"))
tokenHash = m.hexdigest()
supposedUsername = glob.db.fetch("SELECT users.username FROM users LEFT JOIN irc_tokens ON users.id = irc_tokens.userid WHERE irc_tokens.token = %s LIMIT 1", [tokenHash])
if supposedUsername:
self.supposedUsername = supposedUsername["username"]
self.__handleCommand = self.registerHandler
else:
# Wrong IRC Token
self.reply("464 :Password incorrect")
elif command == "QUIT":
self.disconnect()
def registerHandler(self, command, arguments):
"""NICK and USER commands handler"""
if command == "NICK":
if len(arguments) < 1:
self.reply("431 :No nickname given")
return
nick = arguments[0]
# Make sure this is the first time we set our nickname
if self.username != "":
self.reply("432 * %s :Erroneous nickname" % nick)
return
# Make sure the IRC token was correct:
if nick.lower() != self.supposedUsername.lower():
self.reply("464 :Password incorrect")
return
# Make sure we are not connected to Bancho
token = glob.tokens.getTokenFromUsername(nick)
if token != None:
self.reply("433 * {} :Nickname is already in use".format(nick))
return
# Make sure we are not already connected from IRC with that name
for _, value in self.server.clients.items():
if value.username == self.username and value != self:
self.reply("433 * {} :Nickname is already in use".format(nick))
return
# Everything seems fine, set username (nickname)
self.username = nick
elif command == "USER":
# Ignore USER command, we use nickname only
return
elif command == "QUIT":
# Disconnect if we have received a QUIT command
self.disconnect()
return
else:
# Ignore any other command while logging in
return
# If we now have a valid username, connect to bancho and send IRC welcome stuff
if self.username != "":
# Bancho connection
chat.IRCConnect(self.username)
# IRC reply
self.replyCode(1, "Welcome to the Internet Relay Network")
self.replyCode(2, "Your host is {}, running version pep.py-{}".format(self.server.host, glob.VERSION))
self.replyCode(3, "This server was created since the beginning")
self.replyCode(4, "{} pep.py-{} o o".format(self.server.host, glob.VERSION))
self.sendLusers()
self.sendMotd()
self.__handleCommand = self.mainHandler
def quitHandler(self, command, arguments):
"""QUIT command handler"""
self.disconnect(self.username if len(arguments) < 1 else arguments[0])
def joinHandler(self, command, arguments):
"""JOIN command handler"""
if len(arguments) < 1:
self.reply461("JOIN")
return
# Get bancho token object
token = glob.tokens.getTokenFromUsername(self.username)
if token == None:
return
# 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'''
# Get channels to join list
channels = arguments[0].split(",")
for channel in channels:
# Make sure we are not already in that channel
# (we already check this bancho-side, but we need to do it
# also here k maron)
if channel.lower() in token.joinedChannels:
continue
# Attempt to join the channel
response = chat.IRCJoinChannel(self.username, channel)
if response == 0:
# Joined successfully
self.joinedChannels.append(channel)
# Let everyone in this channel know that we've joined
self.messageChannel(channel, "{} JOIN".format(self.username), channel, True)
# Send channel description (topic)
description = glob.channels.channels[channel].description
if description == "":
self.replyCode(331, "No topic is set", channel=channel)
else:
self.replyCode(332, description, channel=channel)
# Build connected users list
users = glob.channels.channels[channel].getConnectedUsers()[:]
usernames = []
for user in users:
token = glob.tokens.getTokenFromUserID(user)
if token == None:
continue
usernames.append(token.username)
usernames = " ".join(usernames)
# Send IRC users lis
self.replyCode(353, usernames, channel="= {}".format(channel))
self.replyCode(366, "End of NAMES list", channel=channel)
elif response == 403:
# Channel doesn't exist (or no read permissions)
self.reply403(channel)
continue
def partHandler(self, command, arguments):
"""PART command handler"""
if len(arguments) < 1:
self.reply461("PART")
return
# Get bancho token object
token = glob.tokens.getTokenFromUsername(self.username)
if token == None:
return
# Get channels to part list
channels = arguments[0].split(",")
for channel in channels:
# Make sure we in that channel
# (we already check this bancho-side, but we need to do it
# also here k maron)
if channel.lower() not in token.joinedChannels:
continue
# Attempt to part the channel
response = chat.IRCPartChannel(self.username, channel)
if response == 0:
# No errors, remove channel from joinedChannels
self.joinedChannels.remove(channel)
elif response == 403:
self.reply403(channel)
elif response == 442:
self.replyCode(442, "You're not on that channel", channel=channel)
def noticePrivmsgHandler(self, command, arguments):
"""NOTICE and PRIVMSG commands handler (same syntax)"""
# Syntax check
if len(arguments) == 0:
self.replyCode(411, "No recipient given ({})".format(command))
return
if len(arguments) == 1:
self.replyCode(412, "No text to send")
return
recipient = arguments[0]
message = arguments[1]
# Send the message to bancho and reply
response = chat.sendMessage(self.username, recipient, message, toIRC=False)
if response == 404:
self.replyCode(404, "Cannot send to channel", channel=recipient)
return
elif response == 403:
self.replyCode(403, "No such channel", channel=recipient)
return
elif response == 401:
self.replyCode(401, "No such nick/channel", channel=recipient)
return
# Send the message to IRC and bancho
if recipient.startswith("#"):
# Public message (IRC)
if recipient not in glob.channels.channels:
self.replyCode(401, "No such nick/channel", channel=recipient)
return
for _, value in self.server.clients.items():
if recipient in value.joinedChannels and value != self:
value.message(":{} PRIVMSG {} :{}".format(self.username, recipient, message))
#self.messageChannel(recipient, command, "{} :{}".format(recipient, message))
else:
# Private message (IRC)
for _, value in self.server.clients.items():
if value.username == recipient:
value.message(":{} PRIVMSG {} :{}".format(self.username, recipient, message))
def motdHandler(self, command, arguments):
"""MOTD command handler"""
self.sendMotd()
def lusersHandler(self, command, arguments):
"""LUSERS command handler"""
self.sendLusers()
def pingHandler(self, command, arguments):
"""PING command handler"""
if len(arguments) < 1:
self.replyCode(409, "No origin specified")
return
self.reply("PONG {} :{}".format(self.server.host, arguments[0]))
def pongHandler(self, command, arguments):
"""(fake) PONG command handler"""
pass
def mainHandler(self, command, arguments):
"""Handler for post-login commands"""
handlers = {
#"AWAY": away_handler,
#"ISON": ison_handler,
"JOIN": self.joinHandler,
#"LIST": list_handler,
"LUSERS": self.lusersHandler,
#"MODE": mode_handler,
"MOTD": self.motdHandler,
#"NICK": nick_handler,
#"NOTICE": notice_and_privmsg_handler,
"PART": self.partHandler,
"PING": self.pingHandler,
"PONG": self.pongHandler,
"PRIVMSG": self.noticePrivmsgHandler,
"QUIT": self.quitHandler,
#"TOPIC": topic_handler,
#"WALLOPS": wallops_handler,
#"WHO": who_handler,
#"WHOIS": whois_handler,
"USER": self.dummyHandler,
}
try:
handlers[command](command, arguments)
except KeyError:
self.replyCode(421, "Unknown command ({})".format(command))
class Server:
def __init__(self, port):
self.host = socket.getfqdn("127.0.0.1")[:63]
self.port = port
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):
"""
Disconnect someone from IRC if connected
username -- victim
"""
for _, value in self.clients.items():
if value.username == username:
value.disconnect(callLogout=False)
break# or dictionary changes size during iteration
def banchoJoinChannel(self, username, channel):
"""
Let every IRC client connected to a specific client know that 'username' joined the channel from bancho
username -- username of bancho user
channel -- joined channel name
"""
for _, value in self.clients.items():
if channel in value.joinedChannels:
value.message(":{} JOIN {}".format(username, channel))
def banchoPartChannel(self, username, channel):
"""
Let every IRC client connected to a specific client know that 'username' parted the channel from bancho
username -- username of bancho user
channel -- joined channel name
"""
for _, value in self.clients.items():
if channel in value.joinedChannels:
value.message(":{} PART {}".format(username, channel))
def banchoMessage(self, fro, to, message):
"""
Send a message to IRC when someone sends it from bancho
fro -- sender username
to -- receiver username
message -- text of the message
"""
if to.startswith("#"):
# Public message
for _, value in self.clients.items():
if to in value.joinedChannels and value.username != fro:
value.message(":{} PRIVMSG {} :{}".format(fro, to, message))
else:
# Private message
for _, value in self.clients.items():
if value.username == to and value.username != fro:
value.message(":{} PRIVMSG {} :{}".format(fro, to, message))
def removeClient(self, client, quitmsg):
"""
Remove a client from connected clients
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"""
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
serversocket.bind(("127.0.0.1", self.port))
except socket.error as e:
log.error("[IRC] Could not bind port {}:{}".format(self.port, e))
sys.exit(1)
serversocket.listen(5)
lastAliveCheck = time.time()
# Main server loop
while True:
(iwtd, owtd, ewtd) = select.select(
[serversocket] + [x.socket for x in self.clients.values()],
[x.socket for x in self.clients.values()
if x.writeBufferSize() > 0],
[],
2)
# Handle incoming connections
for x in iwtd:
if x in self.clients:
self.clients[x].readSocket()
else:
(conn, addr) = x.accept()
try:
self.clients[conn] = Client(self, conn)
log.info("[IRC] Accepted connection from {}:{}".format(addr[0], addr[1]))
except socket.error as e:
try:
conn.close()
except:
pass
# Handle outgoing connections
for x in owtd:
if x in self.clients: # client may have been disconnected
self.clients[x].writeSocket()
# Make sure all IRC clients are still connected
now = time.time()
if lastAliveCheck + 10 < now:
for client in list(self.clients.values()):
client.checkAlive()
lastAliveCheck = now
def main(port=6667):
glob.ircServer = Server(port)
glob.ircServer.start()

View File

@ -1,3 +1,5 @@
from objects import glob
class channel:
"""
A chat channel
@ -10,15 +12,7 @@ class channel:
moderated -- bool
"""
name = ""
description = ""
connectedUsers = []
publicRead = False
publicWrite = False
moderated = False
def __init__(self, __name, __description, __publicRead, __publicWrite):
def __init__(self, __name, __description, __publicRead, __publicWrite, temp):
"""
Create a new chat channel object
@ -26,13 +20,23 @@ class channel:
__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.
"""
self.name = __name
self.description = __description
self.publicRead = __publicRead
self.publicWrite = __publicWrite
self.connectedUsers = []
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)
# Client name (#spectator/#multiplayer)
self.clientName = self.name
if self.name.startswith("#spect_"):
self.clientName = "#spectator"
elif self.name.startswith("#multi_"):
self.clientName = "#multiplayer"
def userJoin(self, __userID):
@ -53,9 +57,13 @@ class channel:
__userID -- user ID that left the channel
"""
connectedUsers = self.connectedUsers
if __userID in connectedUsers:
connectedUsers.remove(__userID)
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):
@ -64,7 +72,6 @@ class channel:
return -- connectedUsers list
"""
return self.connectedUsers
@ -74,5 +81,4 @@ class channel:
return -- connected users number
"""
return len(self.connectedUsers)

View File

@ -1,5 +1,6 @@
from objects import glob
from objects import channel
from helpers import logHelper as log
class channelList:
"""
@ -27,14 +28,40 @@ class channelList:
self.addChannel(i["name"], i["description"], publicRead, publicWrite)
def addChannel(self, __name, __description, __publicRead, __publicWrite):
def addChannel(self, name, description, publicRead, publicWrite, temp = False):
"""
Add a channel object to channels dictionary
__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
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.
"""
self.channels[name] = channel.channel(name, description, publicRead, publicWrite, temp)
log.info("Created channel {}".format(name))
self.channels[__name] = channel.channel(__name, __description, __publicRead, __publicWrite)
def addTempChannel(self, name):
"""
Add a temporary channel (like #spectator or #multiplayer), gets deleted when there's no one in the channel
name -- channel name
return -- True if channel was created, False if failed
"""
if name in self.channels:
return False
self.channels[name] = channel.channel(name, "Chat", True, True, True)
log.info("Created temp channel {}".format(name))
def removeChannel(self, name):
"""
Removes a channel from channels list
name -- channel name
"""
if name not in self.channels:
log.debug("{} is not in channels list".format(name))
return
self.channels.pop(name)
log.info("Removed channel {}".format(name))

View File

@ -9,6 +9,8 @@ from raven import Client
try:
with open("version") as f:
VERSION = f.read()
if VERSION == "":
raise
except:
VERSION = "¯\_(xd)_/¯"

View File

@ -9,6 +9,7 @@ from constants import serverPackets
from constants import dataTypes
from constants import matchTeams
from helpers import logHelper as log
from helpers import chatHelper as chat
class match:
"""Multiplayer match object"""
@ -61,6 +62,9 @@ class match:
for _ in range(0,16):
self.slots.append({"status": slotStatuses.free, "team": 0, "userID": -1, "mods": 0, "loaded": False, "skip": False, "complete": False})
# Create #multiplayer channel
glob.channels.addTempChannel("#multi_{}".format(self.matchID))
def getMatchData(self):
"""
@ -577,11 +581,11 @@ class match:
# FokaBot is too busy
if to == 999:
froToken.enqueue(serverPackets.sendMessage("FokaBot", froToken.username, "I would love to join your match, but I'm busy keeping ripple up and running. Sorry. Beep Boop."))
chat.sendMessage("FokaBot", froToken.username, "I would love to join your match, but I'm busy keeping ripple up and running. Sorry. Beep Boop.")
# Send message
message = "Come join my multiplayer match: \"[osump://{}/{} {}]\"".format(self.matchID, self.matchPassword.replace(" ", "_"), self.matchName)
toToken.enqueue(serverPackets.sendMessage(froToken.username, toToken.username, message))
chat.sendMessage(token=froToken, to=toToken.username, message=message)
def countUsers(self):

View File

@ -9,6 +9,7 @@ import uuid
import time
import threading
from helpers import logHelper as log
from helpers import chatHelper as chat
class token:
"""
@ -34,7 +35,7 @@ class token:
"""
def __init__(self, __userID, token = None, ip = ""):
def __init__(self, __userID, token = None, ip = "", irc = False):
"""
Create a token object and set userID and token
@ -42,6 +43,7 @@ class 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.
"""
# Set stuff
@ -49,6 +51,7 @@ class token:
self.username = userHelper.getUsername(self.userID)
self.privileges = userHelper.getPrivileges(self.userID)
self.admin = userHelper.isInPrivilegeGroup(self.userID, "developer") or userHelper.isInPrivilegeGroup(self.userID, "community manager")
self.irc = irc
self.restricted = userHelper.isRestricted(self.userID)
self.loginTime = int(time.time())
self.pingTime = self.loginTime
@ -78,7 +81,6 @@ class token:
self.actionMd5 = ""
self.actionMods = 0
self.gameMode = gameModes.std
self.rankedScore = 0
self.accuracy = 0.0
self.playcount = 0
@ -100,8 +102,9 @@ class token:
userHelper.saveBanchoSession(self.userID, self.ip)
# If we are restricted, send message from FokaBot to user
if self.restricted == True:
self.setRestricted()
# NOTE: Sent later
#if self.restricted == True:
# self.setRestricted()
def enqueue(self, __bytes):
@ -110,8 +113,8 @@ class token:
__bytes -- (packet) bytes to enqueue
"""
self.queue += __bytes
if self.irc == False:
self.queue += __bytes
def resetQueue(self):
@ -228,11 +231,11 @@ class token:
"""Set match to -1"""
self.matchID = -1
def kick(self):
def kick(self, message="You have been kicked from the server. Please login again."):
"""Kick this user from the server"""
# Send packet to target
log.info("{} has been disconnected. (kick)".format(self.username))
self.enqueue(serverPackets.notification("You have been kicked from the server. Please login again."))
self.enqueue(serverPackets.notification(message))
self.enqueue(serverPackets.loginFailed())
# Logout event
@ -301,10 +304,22 @@ class token:
self.gameRank = stats["gameRank"]
self.pp = stats["pp"]
def checkRestricted(self, force=False):
"""
Check if this token is restricted. If so, send fokabot message
force -- If True, get restricted value from db.
If false, get the cached one. Optional. Default: False
"""
if force == True:
self.restricted = userHelper.isRestricted(self.userID)
if self.restricted == True:
self.setRestricted()
def setRestricted(self):
"""
Set this token as restricted, send FokaBot message to user
and send offline packet to everyone
"""
self.restricted = True
self.enqueue(serverPackets.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.")

View File

@ -19,15 +19,16 @@ class tokenList:
"""
self.tokens = {}
def addToken(self, userID, ip = ""):
def addToken(self, userID, ip = "", irc = False):
"""
Add a token object to tokens list
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)
newToken = osuToken.token(userID, ip=ip, irc=irc)
self.tokens[newToken.token] = newToken
return newToken
@ -40,7 +41,8 @@ class tokenList:
if token in self.tokens:
# Delete session from DB
userHelper.deleteBanchoSessions(self.tokens[token].userID, self.tokens[token].ip)
if self.tokens[token].ip != "":
userHelper.deleteBanchoSessions(self.tokens[token].userID, self.tokens[token].ip)
# Pop token from list
self.tokens.pop(token)
@ -108,13 +110,14 @@ class tokenList:
"""
# Delete older tokens
for key, value in self.tokens.items():
for key, value in list(self.tokens.items()):
if value.userID == userID:
# Delete this token from the dictionary
self.tokens.pop(key)
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.")
#self.tokens.pop(key)
# break or items() function throws errors
break
#break
def multipleEnqueue(self, packet, who, but = False):
@ -136,8 +139,6 @@ class tokenList:
if shouldEnqueue:
value.enqueue(packet)
def enqueueAll(self, packet):
"""
Enqueue packet(s) to every connected user
@ -162,7 +163,7 @@ class tokenList:
timeoutLimit = time.time()-__timeoutTime
for key, value in self.tokens.items():
# Check timeout (fokabot is ignored)
if value.pingTime < timeoutLimit and value.userID != 999:
if value.pingTime < timeoutLimit and value.userID != 999 and value.irc == False:
# That user has timed out, add to disconnected tokens
# We can't delete it while iterating or items() throws an error
timedOutTokens.append(key)
@ -195,3 +196,18 @@ class tokenList:
Call at bancho startup to delete old cached sessions
"""
glob.db.execute("TRUNCATE TABLE bancho_sessions")
def tokenExists(self, username = "", userID = -1):
"""
Check if a token exists (aka check if someone is connected)
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

26
pep.py
View File

@ -2,6 +2,7 @@
import sys
import os
from multiprocessing.pool import ThreadPool
import threading
# Tornado
import tornado.ioloop
@ -30,6 +31,7 @@ from handlers import apiOnlineUsersHandler
from handlers import apiServerStatusHandler
from handlers import ciTriggerHandler
from irc import ircserver
def make_app():
return tornado.web.Application([
@ -67,8 +69,9 @@ if __name__ == "__main__":
# Connect to db
try:
print("> Connecting to MySQL db... ")
consoleHelper.printNoNl("> Connecting to MySQL db")
glob.db = databaseHelperNew.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()
except:
# Exception while connecting to db
@ -109,7 +112,7 @@ if __name__ == "__main__":
consoleHelper.printDone()
# Initialize chat channels
consoleHelper.printNoNl("> Initializing chat channels... ")
print("> Initializing chat channels... ")
glob.channels.loadChannels()
consoleHelper.printDone()
@ -129,7 +132,7 @@ if __name__ == "__main__":
consoleHelper.printDone()
# Localize warning
glob.localize = generalFunctions.stringToBool(glob.conf.config["server"]["localize"])
glob.localize = generalFunctions.stringToBool(glob.conf.config["localize"]["enable"])
if glob.localize == False:
consoleHelper.printColored("[!] Warning! Users localization is disabled!", bcolors.YELLOW)
@ -151,6 +154,20 @@ if __name__ == "__main__":
if glob.debug == True:
consoleHelper.printColored("[!] Warning! Server running in debug mode!", bcolors.YELLOW)
# IRC start message and console output
glob.irc = generalFunctions.stringToBool(glob.conf.config["irc"]["enable"])
if glob.irc == True:
# IRC port
try:
ircPort = int(glob.conf.config["irc"]["port"])
except:
consoleHelper.printColored("[!] Invalid IRC port! Please check your config.ini and run the server again", bcolors.RED)
log.logMessage("IRC server started!", discord=True, of="info.txt", stdout=False)
consoleHelper.printColored("> IRC server listening on 127.0.0.1:{}...".format(ircPort), bcolors.GREEN)
threading.Thread(target=lambda: ircserver.main(port=ircPort)).start()
else:
consoleHelper.printColored("[!] Warning! IRC server is disabled!", bcolors.YELLOW)
# Server port
try:
serverPort = int(glob.conf.config["server"]["port"])
@ -158,7 +175,6 @@ if __name__ == "__main__":
consoleHelper.printColored("[!] Invalid server port! Please check your config.ini and run the server again", bcolors.RED)
# Make app
#application = tornado.httpserver.HTTPServer(make_app())
application = make_app()
# Set up sentry
@ -176,7 +192,7 @@ if __name__ == "__main__":
# Server start message and console output
log.logMessage("Server started!", discord=True, of="info.txt", stdout=False)
consoleHelper.printColored("> Tornado listening for 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)
# Start tornado
application.listen(serverPort)

View File

@ -1 +1 @@
1.4.0
1.6.1