Compare commits
	
		
			1 Commits
		
	
	
		
			v1.10.0
			...
			privileges
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					7b06f2921e | 
							
								
								
									
										2
									
								
								common
									
									
									
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								common
									
									
									
									
									
								
							 Submodule common updated: db36e8d589...cccd620817
									
								
							@@ -1,3 +1,4 @@
 | 
			
		||||
""" Contains functions used to read specific client packets from byte stream """
 | 
			
		||||
from constants import dataTypes
 | 
			
		||||
from helpers import packetHelper
 | 
			
		||||
from constants import slotStatuses
 | 
			
		||||
@@ -99,7 +100,7 @@ def matchSettings(stream):
 | 
			
		||||
	start += 2
 | 
			
		||||
	for i in range(0,16):
 | 
			
		||||
		s = data[0]["slot{}Status".format(str(i))]
 | 
			
		||||
		if s != slotStatuses.FREE and s != slotStatuses.LOCKED:
 | 
			
		||||
		if s != slotStatuses.free and s != slotStatuses.locked:
 | 
			
		||||
			start += 4
 | 
			
		||||
 | 
			
		||||
	# Other settings
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
"""Bancho exceptions"""
 | 
			
		||||
# TODO: Prints in exceptions
 | 
			
		||||
class loginFailedException(Exception):
 | 
			
		||||
	pass
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -19,12 +19,12 @@ from objects import glob
 | 
			
		||||
Commands callbacks
 | 
			
		||||
 | 
			
		||||
Must have fro, chan and messages as arguments
 | 
			
		||||
:param fro: username of who triggered the command
 | 
			
		||||
:param chan: channel"(or username, if PM) where the message was sent
 | 
			
		||||
:param message: list containing arguments passed from the message
 | 
			
		||||
				[0] = first argument
 | 
			
		||||
				[1] = second argument
 | 
			
		||||
				. . .
 | 
			
		||||
fro -- name of who triggered the command
 | 
			
		||||
chan -- channel where the message was sent
 | 
			
		||||
message -- 	list containing arguments passed from the message
 | 
			
		||||
			[0] = first argument
 | 
			
		||||
			[1] = second argument
 | 
			
		||||
			. . .
 | 
			
		||||
 | 
			
		||||
return the message or **False** if there's no response by the bot
 | 
			
		||||
TODO: Change False to None, because False doesn't make any sense
 | 
			
		||||
@@ -35,7 +35,6 @@ def instantRestart(fro, chan, message):
 | 
			
		||||
	return False
 | 
			
		||||
 | 
			
		||||
def faq(fro, chan, message):
 | 
			
		||||
	# TODO: Unhardcode this
 | 
			
		||||
	if message[0] == "rules":
 | 
			
		||||
		return "Please make sure to check (Ripple's rules)[http://ripple.moe/?p=23]."
 | 
			
		||||
	elif message[0] == "swearing":
 | 
			
		||||
@@ -161,11 +160,11 @@ def silence(fro, chan, message):
 | 
			
		||||
	if unit == 's':
 | 
			
		||||
		silenceTime = int(amount)
 | 
			
		||||
	elif unit == 'm':
 | 
			
		||||
		silenceTime = int(amount) * 60
 | 
			
		||||
		silenceTime = int(amount)*60
 | 
			
		||||
	elif unit == 'h':
 | 
			
		||||
		silenceTime = int(amount) * 3600
 | 
			
		||||
		silenceTime = int(amount)*3600
 | 
			
		||||
	elif unit == 'd':
 | 
			
		||||
		silenceTime = int(amount) * 86400
 | 
			
		||||
		silenceTime = int(amount)*86400
 | 
			
		||||
	else:
 | 
			
		||||
		return "Invalid time unit (s/m/h/d)."
 | 
			
		||||
 | 
			
		||||
@@ -360,13 +359,7 @@ def systemStatus(fro, chan, message):
 | 
			
		||||
	data = systemHelper.getSystemInfo()
 | 
			
		||||
 | 
			
		||||
	# Final message
 | 
			
		||||
	letsVersion = glob.redis.get("lets:version")
 | 
			
		||||
	if letsVersion is None:
 | 
			
		||||
		letsVersion = "\_(xd)_/"
 | 
			
		||||
	else:
 | 
			
		||||
		letsVersion = letsVersion.decode("utf-8")
 | 
			
		||||
	msg = "pep.py bancho server v{}\n".format(glob.VERSION)
 | 
			
		||||
	msg += "LETS scores server v{}\n".format(letsVersion)
 | 
			
		||||
	msg += "made by the Ripple team\n"
 | 
			
		||||
	msg += "\n"
 | 
			
		||||
	msg += "=== BANCHO STATS ===\n"
 | 
			
		||||
@@ -714,6 +707,11 @@ callback: function to call when the command is triggered. Optional.
 | 
			
		||||
response: text to return when the command is triggered. Optional.
 | 
			
		||||
syntax: command syntax. Arguments must be separated by spaces (eg: <arg1> <arg2>)
 | 
			
		||||
privileges: privileges needed to execute the command. Optional.
 | 
			
		||||
 | 
			
		||||
NOTES:
 | 
			
		||||
- You CAN'T use both rank and minRank at the same time.
 | 
			
		||||
- If both rank and minrank are **not** present, everyone will be able to run that command.
 | 
			
		||||
- You MUST set trigger and callback/response, or the command won't work.
 | 
			
		||||
"""
 | 
			
		||||
commands = [
 | 
			
		||||
	{
 | 
			
		||||
 
 | 
			
		||||
@@ -1,2 +1,2 @@
 | 
			
		||||
NORMAL = 0
 | 
			
		||||
FREE_MOD = 1
 | 
			
		||||
normal = 0
 | 
			
		||||
freeMod = 1
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,3 @@
 | 
			
		||||
SCORE = 0
 | 
			
		||||
ACCURACY = 1
 | 
			
		||||
COMBO = 2
 | 
			
		||||
score = 0
 | 
			
		||||
accuracy = 1
 | 
			
		||||
combo = 2
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
HEAD_TO_HEAD = 0
 | 
			
		||||
TAG_COOP = 1
 | 
			
		||||
TEAM_VS = 2
 | 
			
		||||
TAG_TEAM_VS = 3
 | 
			
		||||
headToHead = 0
 | 
			
		||||
tagCoop = 1
 | 
			
		||||
teamVs = 2
 | 
			
		||||
tagTeamVs = 3
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,3 @@
 | 
			
		||||
NO_TEAM = 0
 | 
			
		||||
BLUE = 1
 | 
			
		||||
RED = 2
 | 
			
		||||
noTeam = 0
 | 
			
		||||
blue = 1
 | 
			
		||||
red = 2
 | 
			
		||||
 
 | 
			
		||||
@@ -159,7 +159,7 @@ def channelInfo(chan):
 | 
			
		||||
	return packetHelper.buildPacket(packetIDs.server_channelInfo, [
 | 
			
		||||
		[chan, dataTypes.STRING],
 | 
			
		||||
		[channel.description, dataTypes.STRING],
 | 
			
		||||
		[len(channel.connectedUsers), dataTypes.UINT16]
 | 
			
		||||
		[channel.getConnectedUsersCount(), dataTypes.UINT16]
 | 
			
		||||
	])
 | 
			
		||||
 | 
			
		||||
def channelInfoEnd():
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
FREE = 1
 | 
			
		||||
LOCKED = 2
 | 
			
		||||
NOT_READY = 4
 | 
			
		||||
READY = 8
 | 
			
		||||
NO_MAP = 16
 | 
			
		||||
PLAYING = 32
 | 
			
		||||
OCCUPIED = 124
 | 
			
		||||
PLAYING_QUIT = 128
 | 
			
		||||
free = 1
 | 
			
		||||
locked = 2
 | 
			
		||||
notReady = 4
 | 
			
		||||
ready = 8
 | 
			
		||||
noMap = 16
 | 
			
		||||
playing = 32
 | 
			
		||||
occupied = 124
 | 
			
		||||
playingQuit = 128
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
"""Bancho user ranks"""
 | 
			
		||||
NORMAL			= 0
 | 
			
		||||
PLAYER			= 1
 | 
			
		||||
BAT				= 2
 | 
			
		||||
 
 | 
			
		||||
@@ -11,14 +11,18 @@ def handle(userToken, packetData):
 | 
			
		||||
	userID = userToken.userID
 | 
			
		||||
	username = userToken.username
 | 
			
		||||
 | 
			
		||||
	# Update privileges
 | 
			
		||||
	userToken.updatePrivileges()
 | 
			
		||||
 | 
			
		||||
	# Make sure we are not banned
 | 
			
		||||
	if userUtils.isBanned(userID):
 | 
			
		||||
	if userUtils.isBanned(priv=userToken.privileges):
 | 
			
		||||
		userToken.enqueue(serverPackets.loginBanned())
 | 
			
		||||
		return
 | 
			
		||||
 | 
			
		||||
	# Send restricted message if needed
 | 
			
		||||
	if userToken.restricted:
 | 
			
		||||
		userToken.checkRestricted(True)
 | 
			
		||||
	if not userToken.restricted:
 | 
			
		||||
		if userUtils.isRestricted(priv=userToken.privileges):
 | 
			
		||||
			userToken.setRestricted()
 | 
			
		||||
 | 
			
		||||
	# Change action packet
 | 
			
		||||
	packetData = clientPackets.userActionChange(packetData)
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@ def handle(userToken, packetData):
 | 
			
		||||
	match = glob.matches.matches[matchID]
 | 
			
		||||
 | 
			
		||||
	# Set slot or match mods according to modType
 | 
			
		||||
	if match.matchModMode == matchModModes.FREE_MOD:
 | 
			
		||||
	if match.matchModMode == matchModModes.freeMod:
 | 
			
		||||
		# Freemod
 | 
			
		||||
		# Host can set global DT/HT
 | 
			
		||||
		if userID == match.hostUserID:
 | 
			
		||||
 
 | 
			
		||||
@@ -81,11 +81,11 @@ def handle(userToken, packetData):
 | 
			
		||||
	# Reset ready if needed
 | 
			
		||||
	if oldMods != match.mods or oldBeatmapMD5 != match.beatmapMD5:
 | 
			
		||||
		for i in range(0,16):
 | 
			
		||||
			if match.slots[i].status == slotStatuses.READY:
 | 
			
		||||
				match.slots[i].status = slotStatuses.NOT_READY
 | 
			
		||||
			if match.slots[i].status == slotStatuses.ready:
 | 
			
		||||
				match.slots[i].status = slotStatuses.notReady
 | 
			
		||||
 | 
			
		||||
	# Reset mods if needed
 | 
			
		||||
	if match.matchModMode == matchModModes.NORMAL:
 | 
			
		||||
	if match.matchModMode == matchModModes.normal:
 | 
			
		||||
		# Reset slot mods if not freeMods
 | 
			
		||||
		for i in range(0,16):
 | 
			
		||||
			match.slots[i].mods = 0
 | 
			
		||||
@@ -94,21 +94,21 @@ def handle(userToken, packetData):
 | 
			
		||||
		match.mods = 0
 | 
			
		||||
 | 
			
		||||
	# Set/reset teams
 | 
			
		||||
	if match.matchTeamType == matchTeamTypes.TEAM_VS or match.matchTeamType == matchTeamTypes.TAG_TEAM_VS:
 | 
			
		||||
	if match.matchTeamType == matchTeamTypes.teamVs or match.matchTeamType == matchTeamTypes.tagTeamVs:
 | 
			
		||||
		# Set teams
 | 
			
		||||
		c=0
 | 
			
		||||
		for i in range(0,16):
 | 
			
		||||
			if match.slots[i].team == matchTeams.NO_TEAM:
 | 
			
		||||
				match.slots[i].team = matchTeams.RED if c % 2 == 0 else matchTeams.BLUE
 | 
			
		||||
			if match.slots[i].team == matchTeams.noTeam:
 | 
			
		||||
				match.slots[i].team = matchTeams.red if c % 2 == 0 else matchTeams.blue
 | 
			
		||||
				c+=1
 | 
			
		||||
	else:
 | 
			
		||||
		# Reset teams
 | 
			
		||||
		for i in range(0,16):
 | 
			
		||||
			match.slots[i].team = matchTeams.NO_TEAM
 | 
			
		||||
			match.slots[i].team = matchTeams.noTeam
 | 
			
		||||
 | 
			
		||||
	# Force no freemods if tag coop
 | 
			
		||||
	if match.matchTeamType == matchTeamTypes.TAG_COOP or match.matchTeamType == matchTeamTypes.TAG_TEAM_VS:
 | 
			
		||||
		match.matchModMode = matchModModes.NORMAL
 | 
			
		||||
	if match.matchTeamType == matchTeamTypes.tagCoop or match.matchTeamType == matchTeamTypes.tagTeamVs:
 | 
			
		||||
		match.matchModMode = matchModModes.normal
 | 
			
		||||
 | 
			
		||||
	# Send updated settings
 | 
			
		||||
	match.sendUpdates()
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,7 @@ def handle(tornadoRequest):
 | 
			
		||||
		splitData = loginData[2].split("|")
 | 
			
		||||
		osuVersion = splitData[0]
 | 
			
		||||
		timeOffset = int(splitData[1])
 | 
			
		||||
		print(str(timeOffset))
 | 
			
		||||
		clientData = splitData[3].split(":")[:5]
 | 
			
		||||
		if len(clientData) < 4:
 | 
			
		||||
			raise exceptions.forceUpdateException()
 | 
			
		||||
@@ -63,9 +64,9 @@ def handle(tornadoRequest):
 | 
			
		||||
 | 
			
		||||
		# Make sure we are not banned or locked
 | 
			
		||||
		priv = userUtils.getPrivileges(userID)
 | 
			
		||||
		if userUtils.isBanned(userID) == True and priv & privileges.USER_PENDING_VERIFICATION == 0:
 | 
			
		||||
		if userUtils.isBanned(priv=priv) == True and not userUtils.isPending(priv=priv):
 | 
			
		||||
			raise exceptions.loginBannedException()
 | 
			
		||||
		if userUtils.isLocked(userID) == True and priv & privileges.USER_PENDING_VERIFICATION == 0:
 | 
			
		||||
		if userUtils.isLocked(priv=priv) == True and not userUtils.isPending(priv=priv):
 | 
			
		||||
			raise exceptions.loginLockedException()
 | 
			
		||||
 | 
			
		||||
		# 2FA check
 | 
			
		||||
@@ -191,24 +192,23 @@ def handle(tornadoRequest):
 | 
			
		||||
		# Get location and country from ip.zxq.co or database
 | 
			
		||||
		if glob.localize:
 | 
			
		||||
			# Get location and country from IP
 | 
			
		||||
			latitude, longitude = locationHelper.getLocation(requestIP)
 | 
			
		||||
			location = locationHelper.getLocation(requestIP)
 | 
			
		||||
			countryLetters = locationHelper.getCountry(requestIP)
 | 
			
		||||
			country = countryHelper.getCountryID(countryLetters)
 | 
			
		||||
 | 
			
		||||
			# Set country in db if user has no country (first bancho login)
 | 
			
		||||
			if userUtils.getCountry(userID) == "XX":
 | 
			
		||||
				userUtils.setCountry(userID, countryLetters)
 | 
			
		||||
		else:
 | 
			
		||||
			# Set location to 0,0 and get country from db
 | 
			
		||||
			log.warning("Location skipped")
 | 
			
		||||
			latitude = 0
 | 
			
		||||
			longitude = 0
 | 
			
		||||
			location = [0,0]
 | 
			
		||||
			countryLetters = "XX"
 | 
			
		||||
			country = countryHelper.getCountryID(userUtils.getCountry(userID))
 | 
			
		||||
 | 
			
		||||
		# Set location and country
 | 
			
		||||
		responseToken.setLocation(latitude, longitude)
 | 
			
		||||
		responseToken.country = country
 | 
			
		||||
 | 
			
		||||
		# Set country in db if user has no country (first bancho login)
 | 
			
		||||
		if userUtils.getCountry(userID) == "XX":
 | 
			
		||||
			userUtils.setCountry(userID, countryLetters)
 | 
			
		||||
		responseToken.setLocation(location)
 | 
			
		||||
		responseToken.setCountry(country)
 | 
			
		||||
 | 
			
		||||
		# Send to everyone our userpanel if we are not restricted or tournament
 | 
			
		||||
		if not responseToken.restricted:
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ def handle(userToken, packetData):
 | 
			
		||||
	packetData = clientPackets.setAwayMessage(packetData)
 | 
			
		||||
 | 
			
		||||
	# Set token away message
 | 
			
		||||
	userToken.awayMessage = packetData["awayMessage"]
 | 
			
		||||
	userToken.setAwayMessage(packetData["awayMessage"])
 | 
			
		||||
 | 
			
		||||
	# Send private message from fokabot
 | 
			
		||||
	if packetData["awayMessage"] == "":
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ class handler(requestsManager.asyncRequestHandler):
 | 
			
		||||
			if key is None or key != glob.conf.config["server"]["cikey"]:
 | 
			
		||||
				raise exceptions.invalidArgumentsException()
 | 
			
		||||
 | 
			
		||||
			log.info("API REQUEST FOR FOKABOT MESSAGE AAAAAAA")
 | 
			
		||||
			chatHelper.sendMessage("FokaBot", self.get_argument("to"), self.get_argument("msg"))
 | 
			
		||||
 | 
			
		||||
			# Status code and message
 | 
			
		||||
@@ -34,5 +35,7 @@ class handler(requestsManager.asyncRequestHandler):
 | 
			
		||||
			data["status"] = statusCode
 | 
			
		||||
 | 
			
		||||
			# Send response
 | 
			
		||||
			#self.clear()
 | 
			
		||||
			self.write(json.dumps(data))
 | 
			
		||||
			self.set_status(statusCode)
 | 
			
		||||
			self.set_status(statusCode)
 | 
			
		||||
			#self.finish(json.dumps(data))
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@ class handler(requestsManager.asyncRequestHandler):
 | 
			
		||||
			username = None
 | 
			
		||||
			userID = None
 | 
			
		||||
			if "u" in self.request.arguments:
 | 
			
		||||
				username = self.get_argument("u").lower().replace(" ", "_")
 | 
			
		||||
				username = self.get_argument("u")
 | 
			
		||||
			else:
 | 
			
		||||
				try:
 | 
			
		||||
					userID = int(self.get_argument("id"))
 | 
			
		||||
@@ -29,7 +29,7 @@ class handler(requestsManager.asyncRequestHandler):
 | 
			
		||||
				data["result"] = False
 | 
			
		||||
			else:
 | 
			
		||||
				if username is not None:
 | 
			
		||||
					data["result"] = True if glob.tokens.getTokenFromUsername(username, safe=True) is not None else False
 | 
			
		||||
					data["result"] = True if glob.tokens.getTokenFromUsername(username) is not None else False
 | 
			
		||||
				else:
 | 
			
		||||
					data["result"] = True if glob.tokens.getTokenFromUserID(userID) is not None else False
 | 
			
		||||
 | 
			
		||||
@@ -44,5 +44,7 @@ class handler(requestsManager.asyncRequestHandler):
 | 
			
		||||
			data["status"] = statusCode
 | 
			
		||||
 | 
			
		||||
			# Send response
 | 
			
		||||
			#self.clear()
 | 
			
		||||
			self.write(json.dumps(data))
 | 
			
		||||
			self.set_status(statusCode)
 | 
			
		||||
			#self.finish(json.dumps(data))
 | 
			
		||||
 
 | 
			
		||||
@@ -20,5 +20,7 @@ class handler(requestsManager.asyncRequestHandler):
 | 
			
		||||
			data["status"] = statusCode
 | 
			
		||||
 | 
			
		||||
			# Send response
 | 
			
		||||
			#self.clear()
 | 
			
		||||
			self.write(json.dumps(data))
 | 
			
		||||
			self.set_status(statusCode)
 | 
			
		||||
			#self.finish(json.dumps(data))
 | 
			
		||||
 
 | 
			
		||||
@@ -20,5 +20,7 @@ class handler(requestsManager.asyncRequestHandler):
 | 
			
		||||
			data["status"] = statusCode
 | 
			
		||||
 | 
			
		||||
			# Send response
 | 
			
		||||
			#self.clear()
 | 
			
		||||
			self.write(json.dumps(data))
 | 
			
		||||
			self.set_status(statusCode)
 | 
			
		||||
			#self.finish(json.dumps(data))
 | 
			
		||||
 
 | 
			
		||||
@@ -115,6 +115,8 @@ class handler(SentryMixin, requestsManager.asyncRequestHandler):
 | 
			
		||||
							return wrapper
 | 
			
		||||
 | 
			
		||||
						eventHandler = {
 | 
			
		||||
							# TODO: Rename packets and events
 | 
			
		||||
							# TODO: Host check for multi
 | 
			
		||||
							packetIDs.client_changeAction: handleEvent(changeActionEvent),
 | 
			
		||||
							packetIDs.client_logout: handleEvent(logoutEvent),
 | 
			
		||||
							packetIDs.client_friendAdd: handleEvent(friendAddEvent),
 | 
			
		||||
 
 | 
			
		||||
@@ -12,11 +12,14 @@ def joinChannel(userID = 0, channel = "", token = None, toIRC = True):
 | 
			
		||||
	"""
 | 
			
		||||
	Join a channel
 | 
			
		||||
 | 
			
		||||
	:param userID: user ID of the user that joins the channel. Optional. token can be used instead.
 | 
			
		||||
	:param token: user token object of user that joins the channel. Optional. userID can be used instead.
 | 
			
		||||
	:param channel: channel name
 | 
			
		||||
	:param toIRC: if True, send this channel join event to IRC. Must be true if joining from bancho. Default: True
 | 
			
		||||
	:return: 0 if joined or other IRC code in case of error. Needed only on IRC-side
 | 
			
		||||
	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
 | 
			
		||||
@@ -74,12 +77,15 @@ def partChannel(userID = 0, channel = "", token = None, toIRC = True, kick = Fal
 | 
			
		||||
	"""
 | 
			
		||||
	Part a channel
 | 
			
		||||
 | 
			
		||||
	:param userID: user ID of the user that parts the channel. Optional. token can be used instead.
 | 
			
		||||
	:param token: user token object of user that parts the channel. Optional. userID can be used instead.
 | 
			
		||||
	:param channel: channel name
 | 
			
		||||
	:param toIRC: if True, send this channel join event to IRC. Must be true if joining from bancho. Optional. Default: True
 | 
			
		||||
	:param kick: if True, channel tab will be closed on client. Used when leaving lobby. Optional. Default: False
 | 
			
		||||
	:return: 0 if joined or other IRC code in case of error. Needed only on IRC-side
 | 
			
		||||
	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
 | 
			
		||||
@@ -145,12 +151,15 @@ 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
 | 
			
		||||
	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 = ""
 | 
			
		||||
@@ -222,7 +231,7 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
 | 
			
		||||
				raise exceptions.channelNoPermissionsException
 | 
			
		||||
 | 
			
		||||
			# Everything seems fine, build recipients list and send packet
 | 
			
		||||
			recipients = glob.channels.channels[to].connectedUsers[:]
 | 
			
		||||
			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:
 | 
			
		||||
@@ -303,12 +312,6 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
 | 
			
		||||
 | 
			
		||||
""" 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:
 | 
			
		||||
@@ -323,22 +326,9 @@ def fixUsernameForBancho(username):
 | 
			
		||||
	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:
 | 
			
		||||
	"""
 | 
			
		||||
	userID = userUtils.getID(username)
 | 
			
		||||
	if not userID:
 | 
			
		||||
		log.warning("{} doesn't exist".format(username))
 | 
			
		||||
@@ -349,13 +339,6 @@ def IRCConnect(username):
 | 
			
		||||
	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)
 | 
			
		||||
	if token is None:
 | 
			
		||||
		log.warning("{} doesn't exist".format(username))
 | 
			
		||||
@@ -364,13 +347,6 @@ def IRCDisconnect(username):
 | 
			
		||||
	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
 | 
			
		||||
	"""
 | 
			
		||||
	userID = userUtils.getID(username)
 | 
			
		||||
	if not userID:
 | 
			
		||||
		log.warning("{} doesn't exist".format(username))
 | 
			
		||||
@@ -381,13 +357,6 @@ def IRCJoinChannel(username, channel):
 | 
			
		||||
	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
 | 
			
		||||
	"""
 | 
			
		||||
	userID = userUtils.getID(username)
 | 
			
		||||
	if not userID:
 | 
			
		||||
		log.warning("{} doesn't exist".format(username))
 | 
			
		||||
@@ -395,16 +364,9 @@ def IRCPartChannel(username, channel):
 | 
			
		||||
	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
 | 
			
		||||
	glob.tokens.getTokenFromUserID(userID).setAwayMessage(message)
 | 
			
		||||
	return 305 if message == "" else 306
 | 
			
		||||
@@ -5,9 +5,9 @@ class config:
 | 
			
		||||
	# Check if config.ini exists and load/generate it
 | 
			
		||||
	def __init__(self, file):
 | 
			
		||||
		"""
 | 
			
		||||
		Initialize a config file object
 | 
			
		||||
		Initialize a config object
 | 
			
		||||
 | 
			
		||||
		:param file: file name
 | 
			
		||||
		file -- filename
 | 
			
		||||
		"""
 | 
			
		||||
		self.config = configparser.ConfigParser()
 | 
			
		||||
		self.default = True
 | 
			
		||||
@@ -25,9 +25,9 @@ class config:
 | 
			
		||||
	# Check if config.ini has all needed the keys
 | 
			
		||||
	def checkConfig(self):
 | 
			
		||||
		"""
 | 
			
		||||
		Check is the config file has all required keys
 | 
			
		||||
		Check if this config has the required keys
 | 
			
		||||
 | 
			
		||||
		:return: True if valid, False if not valid
 | 
			
		||||
		return -- True if valid, False if not
 | 
			
		||||
		"""
 | 
			
		||||
		try:
 | 
			
		||||
			# Try to get all the required keys
 | 
			
		||||
@@ -37,11 +37,6 @@ class config:
 | 
			
		||||
			self.config.get("db","database")
 | 
			
		||||
			self.config.get("db","workers")
 | 
			
		||||
 | 
			
		||||
			self.config.get("redis","host")
 | 
			
		||||
			self.config.get("redis","port")
 | 
			
		||||
			self.config.get("redis","database")
 | 
			
		||||
			self.config.get("redis","password")
 | 
			
		||||
 | 
			
		||||
			self.config.get("server","port")
 | 
			
		||||
			self.config.get("server","threads")
 | 
			
		||||
			self.config.get("server","gzip")
 | 
			
		||||
@@ -79,9 +74,7 @@ class config:
 | 
			
		||||
 | 
			
		||||
	def generateDefaultConfig(self):
 | 
			
		||||
		"""
 | 
			
		||||
		Write a default config file to disk
 | 
			
		||||
 | 
			
		||||
		:return:
 | 
			
		||||
		Open and set default keys for that config file
 | 
			
		||||
		"""
 | 
			
		||||
		# Open config.ini in write mode
 | 
			
		||||
		f = open(self.fileName, "w")
 | 
			
		||||
@@ -94,12 +87,6 @@ class config:
 | 
			
		||||
		self.config.set("db", "database", "ripple")
 | 
			
		||||
		self.config.set("db", "workers", "4")
 | 
			
		||||
 | 
			
		||||
		self.config.add_section("redis")
 | 
			
		||||
		self.config.set("redis", "host", "localhost")
 | 
			
		||||
		self.config.set("redis", "port", "6379")
 | 
			
		||||
		self.config.set("redis", "database", "0")
 | 
			
		||||
		self.config.set("redis", "password", "")
 | 
			
		||||
 | 
			
		||||
		self.config.add_section("server")
 | 
			
		||||
		self.config.set("server", "port", "5001")
 | 
			
		||||
		self.config.set("server", "threads", "16")
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,11 @@
 | 
			
		||||
from common.constants import bcolors
 | 
			
		||||
from objects import glob
 | 
			
		||||
 | 
			
		||||
def printServerStartHeader(asciiArt=True):
 | 
			
		||||
def printServerStartHeader(asciiArt):
 | 
			
		||||
	"""
 | 
			
		||||
	Print server start message
 | 
			
		||||
	Print server start header with optional ascii art
 | 
			
		||||
 | 
			
		||||
	:param asciiArt: print BanchoBoat ascii art. Default: True
 | 
			
		||||
	:return:
 | 
			
		||||
	asciiArt -- if True, will print ascii art too
 | 
			
		||||
	"""
 | 
			
		||||
	if asciiArt:
 | 
			
		||||
		print("{}           _                 __".format(bcolors.GREEN))
 | 
			
		||||
@@ -33,43 +32,35 @@ def printServerStartHeader(asciiArt=True):
 | 
			
		||||
 | 
			
		||||
def printNoNl(string):
 | 
			
		||||
	"""
 | 
			
		||||
	Print a string without \n at the end
 | 
			
		||||
	Print string without new line at the end
 | 
			
		||||
 | 
			
		||||
	:param string: string to print
 | 
			
		||||
	:return:
 | 
			
		||||
	string -- string to print
 | 
			
		||||
	"""
 | 
			
		||||
	print(string, end="")
 | 
			
		||||
 | 
			
		||||
def printColored(string, color):
 | 
			
		||||
	"""
 | 
			
		||||
	Print a colored string
 | 
			
		||||
	Print colored string
 | 
			
		||||
 | 
			
		||||
	:param string: string to print
 | 
			
		||||
	:param color: ANSI color code
 | 
			
		||||
	:return:
 | 
			
		||||
	string -- string to print
 | 
			
		||||
	color -- see bcolors.py
 | 
			
		||||
	"""
 | 
			
		||||
	print("{}{}{}".format(color, string, bcolors.ENDC))
 | 
			
		||||
 | 
			
		||||
def printError():
 | 
			
		||||
	"""
 | 
			
		||||
	Print a red "Error"
 | 
			
		||||
 | 
			
		||||
	:return:
 | 
			
		||||
	Print error text FOR LOADING
 | 
			
		||||
	"""
 | 
			
		||||
	printColored("Error", bcolors.RED)
 | 
			
		||||
 | 
			
		||||
def printDone():
 | 
			
		||||
	"""
 | 
			
		||||
	Print a green "Done"
 | 
			
		||||
 | 
			
		||||
	:return:
 | 
			
		||||
	Print error text FOR LOADING
 | 
			
		||||
	"""
 | 
			
		||||
	printColored("Done", bcolors.GREEN)
 | 
			
		||||
 | 
			
		||||
def printWarning():
 | 
			
		||||
	"""
 | 
			
		||||
	Print a yellow "Warning"
 | 
			
		||||
 | 
			
		||||
	:return:
 | 
			
		||||
	Print error text FOR LOADING
 | 
			
		||||
	"""
 | 
			
		||||
	printColored("Warning", bcolors.YELLOW)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
# TODO: Update countries list
 | 
			
		||||
"""Contains all country codes with their osu numeric code"""
 | 
			
		||||
 | 
			
		||||
countryCodes = {
 | 
			
		||||
	"LV": 132,
 | 
			
		||||
	"AD": 3,
 | 
			
		||||
@@ -254,11 +255,12 @@ countryCodes = {
 | 
			
		||||
 | 
			
		||||
def getCountryID(code):
 | 
			
		||||
	"""
 | 
			
		||||
	Get osu country ID from country letters
 | 
			
		||||
	Get country ID for osu client
 | 
			
		||||
 | 
			
		||||
	:param code: country letters (eg: US)
 | 
			
		||||
	:return: country osu code
 | 
			
		||||
	code -- country name abbreviation (eg: US)
 | 
			
		||||
	return -- country code int
 | 
			
		||||
	"""
 | 
			
		||||
 | 
			
		||||
	if code in countryCodes:
 | 
			
		||||
		return countryCodes[code]
 | 
			
		||||
	else:
 | 
			
		||||
@@ -268,9 +270,10 @@ def getCountryLetters(code):
 | 
			
		||||
	"""
 | 
			
		||||
	Get country letters from osu country ID
 | 
			
		||||
 | 
			
		||||
	:param code: osu country ID
 | 
			
		||||
	:return: country letters (XX if not found)
 | 
			
		||||
	code -- country code int
 | 
			
		||||
	return -- country name (2 letters) (XX if code not found)
 | 
			
		||||
	"""
 | 
			
		||||
 | 
			
		||||
	for key, value in countryCodes.items():
 | 
			
		||||
		if value == code:
 | 
			
		||||
			return key
 | 
			
		||||
 
 | 
			
		||||
@@ -7,10 +7,10 @@ from objects import glob
 | 
			
		||||
 | 
			
		||||
def getCountry(ip):
 | 
			
		||||
	"""
 | 
			
		||||
	Get country from IP address using geoip api
 | 
			
		||||
	Get country from IP address
 | 
			
		||||
 | 
			
		||||
	:param ip: IP address
 | 
			
		||||
	:return: country code. XX if invalid.
 | 
			
		||||
	ip -- IP Address
 | 
			
		||||
	return -- Country code (2 letters)
 | 
			
		||||
	"""
 | 
			
		||||
	try:
 | 
			
		||||
		# Try to get country from Pikolo Aul's Go-Sanic ip API
 | 
			
		||||
@@ -22,15 +22,15 @@ def getCountry(ip):
 | 
			
		||||
 | 
			
		||||
def getLocation(ip):
 | 
			
		||||
	"""
 | 
			
		||||
	Get latitude and longitude from IP address using geoip api
 | 
			
		||||
	Get latitude and longitude from IP address
 | 
			
		||||
 | 
			
		||||
	:param ip: IP address
 | 
			
		||||
	:return: (latitude, longitude)
 | 
			
		||||
	ip -- IP address
 | 
			
		||||
	return -- [latitude, longitude]
 | 
			
		||||
	"""
 | 
			
		||||
	try:
 | 
			
		||||
		# Try to get position from Pikolo Aul's Go-Sanic ip API
 | 
			
		||||
		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]))
 | 
			
		||||
		return [float(result[0]), float(result[1])]
 | 
			
		||||
	except:
 | 
			
		||||
		log.error("Error in get position")
 | 
			
		||||
		return (0, 0)
 | 
			
		||||
		return [0,0]
 | 
			
		||||
 
 | 
			
		||||
@@ -3,10 +3,10 @@ from constants import dataTypes
 | 
			
		||||
 | 
			
		||||
def uleb128Encode(num):
 | 
			
		||||
	"""
 | 
			
		||||
	Encode an int to uleb128
 | 
			
		||||
	Encode int -> uleb128
 | 
			
		||||
 | 
			
		||||
	:param num: int to encode
 | 
			
		||||
	:return: bytearray with encoded number
 | 
			
		||||
	num -- int to encode
 | 
			
		||||
	return -- bytearray with encoded number
 | 
			
		||||
	"""
 | 
			
		||||
	arr = bytearray()
 | 
			
		||||
	length = 0
 | 
			
		||||
@@ -25,10 +25,10 @@ def uleb128Encode(num):
 | 
			
		||||
 | 
			
		||||
def uleb128Decode(num):
 | 
			
		||||
	"""
 | 
			
		||||
	Decode a uleb128 to int
 | 
			
		||||
	Decode uleb128 -> int
 | 
			
		||||
 | 
			
		||||
	:param num: encoded uleb128 int
 | 
			
		||||
	:return: (total, length)
 | 
			
		||||
	num -- encoded uleb128
 | 
			
		||||
	return -- list. [total, length]
 | 
			
		||||
	"""
 | 
			
		||||
	shift = 0
 | 
			
		||||
	arr = [0,0]	#total, length
 | 
			
		||||
@@ -45,12 +45,14 @@ def uleb128Decode(num):
 | 
			
		||||
 | 
			
		||||
def unpackData(data, dataType):
 | 
			
		||||
	"""
 | 
			
		||||
	Unpacks a single section of a packet.
 | 
			
		||||
	Unpacks data according to dataType
 | 
			
		||||
 | 
			
		||||
	:param data: bytes to unpack
 | 
			
		||||
	:param dataType: data type
 | 
			
		||||
	:return: unpacked bytes
 | 
			
		||||
	data -- bytes array to unpack
 | 
			
		||||
	dataType -- data type. See dataTypes.py
 | 
			
		||||
 | 
			
		||||
	return -- unpacked bytes
 | 
			
		||||
	"""
 | 
			
		||||
 | 
			
		||||
	# Get right pack Type
 | 
			
		||||
	if dataType == dataTypes.UINT16:
 | 
			
		||||
		unpackType = "<H"
 | 
			
		||||
@@ -76,12 +78,14 @@ def unpackData(data, dataType):
 | 
			
		||||
 | 
			
		||||
def packData(__data, dataType):
 | 
			
		||||
	"""
 | 
			
		||||
	Packs a single section of a packet.
 | 
			
		||||
	Packs data according to dataType
 | 
			
		||||
 | 
			
		||||
	:param __data: data to pack
 | 
			
		||||
	:param dataType: data type
 | 
			
		||||
	:return: packed bytes
 | 
			
		||||
	data -- bytes to pack
 | 
			
		||||
	dataType -- data type. See dataTypes.py
 | 
			
		||||
 | 
			
		||||
	return -- packed bytes
 | 
			
		||||
	"""
 | 
			
		||||
 | 
			
		||||
	data = bytes()	# data to return
 | 
			
		||||
	pack = True		# if True, use pack. False only with strings
 | 
			
		||||
 | 
			
		||||
@@ -136,11 +140,12 @@ def packData(__data, dataType):
 | 
			
		||||
 | 
			
		||||
def buildPacket(__packet, __packetData=None):
 | 
			
		||||
	"""
 | 
			
		||||
	Builds a packet
 | 
			
		||||
	Build a packet
 | 
			
		||||
 | 
			
		||||
	:param __packet: packet ID
 | 
			
		||||
	:param __packetData: packet structure [[data, dataType], [data, dataType], ...]
 | 
			
		||||
	:return: packet bytes
 | 
			
		||||
	packet -- packet id (int)
 | 
			
		||||
	packetData -- list [[data, dataType], [data, dataType], ...]
 | 
			
		||||
 | 
			
		||||
	return -- packet bytes
 | 
			
		||||
	"""
 | 
			
		||||
	# Set some variables
 | 
			
		||||
	if __packetData is None:
 | 
			
		||||
@@ -165,31 +170,33 @@ def buildPacket(__packet, __packetData=None):
 | 
			
		||||
 | 
			
		||||
def readPacketID(stream):
 | 
			
		||||
	"""
 | 
			
		||||
	Read packetID (first two bytes) from a packet
 | 
			
		||||
	Read packetID from stream (0-1 bytes)
 | 
			
		||||
 | 
			
		||||
	:param stream: packet bytes
 | 
			
		||||
	:return: packet ID
 | 
			
		||||
	stream -- data stream
 | 
			
		||||
	return -- packet ID (int)
 | 
			
		||||
	"""
 | 
			
		||||
	return unpackData(stream[0:2], dataTypes.UINT16)
 | 
			
		||||
 | 
			
		||||
def readPacketLength(stream):
 | 
			
		||||
	"""
 | 
			
		||||
	Read packet data length (3:7 bytes) from a packet
 | 
			
		||||
	Read packet length from stream (3-4-5-6 bytes)
 | 
			
		||||
 | 
			
		||||
	:param stream: packet bytes
 | 
			
		||||
	:return: packet data length
 | 
			
		||||
	stream -- data stream
 | 
			
		||||
	return -- packet length (int)
 | 
			
		||||
	"""
 | 
			
		||||
	return unpackData(stream[3:7], dataTypes.UINT32)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def readPacketData(stream, structure=None, hasFirstBytes = True):
 | 
			
		||||
	"""
 | 
			
		||||
	Read packet data from `stream` according to `structure`
 | 
			
		||||
	:param stream: packet bytes
 | 
			
		||||
	:param structure: packet structure: [[name, dataType], [name, dataType], ...]
 | 
			
		||||
	:param hasFirstBytes: 	if True, `stream` has packetID and length bytes.
 | 
			
		||||
							if False, `stream` has only packet data. Default: True
 | 
			
		||||
	:return: {name: unpackedValue, ...}
 | 
			
		||||
	Read packet data from stream according to structure
 | 
			
		||||
 | 
			
		||||
	stream -- data stream
 | 
			
		||||
	structure -- [[name, dataType], [name, dataType], ...]
 | 
			
		||||
	hasFirstBytes -- 	if True, stream has packetID and length bytes.
 | 
			
		||||
						if False, stream has only packetData.
 | 
			
		||||
						Optional. Default: True
 | 
			
		||||
	return -- dictionary. key: name, value: read data
 | 
			
		||||
	"""
 | 
			
		||||
	# Read packet ID (first 2 bytes)
 | 
			
		||||
	if structure is None:
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,6 @@ from objects import glob
 | 
			
		||||
def dispose():
 | 
			
		||||
	"""
 | 
			
		||||
	Perform some clean up. Called on shutdown.
 | 
			
		||||
	
 | 
			
		||||
	:return:
 | 
			
		||||
	"""
 | 
			
		||||
	print("> Disposing server... ")
 | 
			
		||||
@@ -28,7 +27,7 @@ def runningUnderUnix():
 | 
			
		||||
	"""
 | 
			
		||||
	Get if the server is running under UNIX or NT
 | 
			
		||||
 | 
			
		||||
	:return: True if running under UNIX, otherwise False
 | 
			
		||||
	return --- True if running under UNIX, otherwise False
 | 
			
		||||
	"""
 | 
			
		||||
	return True if os.name == "posix" else False
 | 
			
		||||
 | 
			
		||||
@@ -36,11 +35,9 @@ def scheduleShutdown(sendRestartTime, restart, message = "", delay=20):
 | 
			
		||||
	"""
 | 
			
		||||
	Schedule a server shutdown/restart
 | 
			
		||||
 | 
			
		||||
	:param sendRestartTime: time (seconds) to wait before sending server restart packets to every client
 | 
			
		||||
	:param restart: if True, server will restart. if False, server will shudown
 | 
			
		||||
	:param message: if set, send that message to every client to warn about the shutdown/restart
 | 
			
		||||
	:param delay: additional restart delay in seconds. Default: 20
 | 
			
		||||
	:return:
 | 
			
		||||
	sendRestartTime -- time (seconds) to wait before sending server restart packets to every client
 | 
			
		||||
	restart -- if True, server will restart. if False, server will shudown
 | 
			
		||||
	message -- if set, send that message to every client to warn about the shutdown/restart
 | 
			
		||||
	"""
 | 
			
		||||
	# Console output
 | 
			
		||||
	log.info("Pep.py will {} in {} seconds!".format("restart" if restart else "shutdown", sendRestartTime+delay))
 | 
			
		||||
@@ -64,21 +61,13 @@ def scheduleShutdown(sendRestartTime, restart, message = "", delay=20):
 | 
			
		||||
	threading.Timer(sendRestartTime+delay, action).start()
 | 
			
		||||
 | 
			
		||||
def restartServer():
 | 
			
		||||
	"""
 | 
			
		||||
	Restart pep.py
 | 
			
		||||
 | 
			
		||||
	:return:
 | 
			
		||||
	"""
 | 
			
		||||
	"""Restart pep.py script"""
 | 
			
		||||
	log.info("Restarting pep.py...")
 | 
			
		||||
	dispose()
 | 
			
		||||
	os.execv(sys.executable, [sys.executable] + sys.argv)
 | 
			
		||||
 | 
			
		||||
def shutdownServer():
 | 
			
		||||
	"""
 | 
			
		||||
	Shutdown pep.py
 | 
			
		||||
 | 
			
		||||
	:return:
 | 
			
		||||
	"""
 | 
			
		||||
	"""Shutdown pep.py"""
 | 
			
		||||
	log.info("Shutting down pep.py...")
 | 
			
		||||
	dispose()
 | 
			
		||||
	sig = signal.SIGKILL if runningUnderUnix() else signal.CTRL_C_EVENT
 | 
			
		||||
@@ -88,7 +77,7 @@ def getSystemInfo():
 | 
			
		||||
	"""
 | 
			
		||||
	Get a dictionary with some system/server info
 | 
			
		||||
 | 
			
		||||
	:return: ["unix", "connectedUsers", "webServer", "cpuUsage", "totalMemory", "usedMemory", "loadAverage"]
 | 
			
		||||
	return -- ["unix", "connectedUsers", "webServer", "cpuUsage", "totalMemory", "usedMemory", "loadAverage"]
 | 
			
		||||
	"""
 | 
			
		||||
	data = {"unix": runningUnderUnix(), "connectedUsers": len(glob.tokens.tokens), "matches": len(glob.matches.matches)}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										125
									
								
								irc/ircserver.py
									
									
									
									
									
								
							
							
						
						
									
										125
									
								
								irc/ircserver.py
									
									
									
									
									
								
							@@ -22,15 +22,18 @@ from objects import glob
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Client:
 | 
			
		||||
	"""
 | 
			
		||||
	IRC Client object
 | 
			
		||||
	"""
 | 
			
		||||
	__linesep_regexp = re.compile(r"\r?\n")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	def __init__(self, server, sock):
 | 
			
		||||
		"""
 | 
			
		||||
		Initialize a Client object
 | 
			
		||||
 | 
			
		||||
		:param server: server object
 | 
			
		||||
		:param sock: socket connection object
 | 
			
		||||
		:return:
 | 
			
		||||
		server -- server object
 | 
			
		||||
		sock -- socket connection object
 | 
			
		||||
		"""
 | 
			
		||||
		self.__timestamp = time.time()
 | 
			
		||||
		self.__readbuffer = ""
 | 
			
		||||
@@ -57,8 +60,7 @@ class Client:
 | 
			
		||||
		Add a message (basic string) to client buffer.
 | 
			
		||||
		This is the lowest possible level.
 | 
			
		||||
 | 
			
		||||
		:param msg: message to add
 | 
			
		||||
		:return:
 | 
			
		||||
		msg -- message to add
 | 
			
		||||
		"""
 | 
			
		||||
		self.__writebuffer += msg + "\r\n"
 | 
			
		||||
 | 
			
		||||
@@ -67,7 +69,7 @@ class Client:
 | 
			
		||||
		"""
 | 
			
		||||
		Return this client's write buffer size
 | 
			
		||||
 | 
			
		||||
		:return: write buffer size
 | 
			
		||||
		return -- write buffer size
 | 
			
		||||
		"""
 | 
			
		||||
		return len(self.__writebuffer)
 | 
			
		||||
 | 
			
		||||
@@ -76,8 +78,7 @@ class Client:
 | 
			
		||||
		"""
 | 
			
		||||
		Add an IRC-like message to client buffer.
 | 
			
		||||
 | 
			
		||||
		:param msg: message (without IRC stuff)
 | 
			
		||||
		:return:
 | 
			
		||||
		msg -- message (without IRC stuff)
 | 
			
		||||
		"""
 | 
			
		||||
		self.message(":{} {}".format(self.server.host, msg))
 | 
			
		||||
 | 
			
		||||
@@ -86,11 +87,10 @@ class Client:
 | 
			
		||||
		"""
 | 
			
		||||
		Add an IRC-like message to client buffer with code
 | 
			
		||||
 | 
			
		||||
		:param code: response code
 | 
			
		||||
		:param message: response message
 | 
			
		||||
		:param nickname: receiver nickname
 | 
			
		||||
		:param channel: optional
 | 
			
		||||
		:return:
 | 
			
		||||
		code -- response code
 | 
			
		||||
		message -- response message
 | 
			
		||||
		nickname -- receiver nickname
 | 
			
		||||
		channel -- optional
 | 
			
		||||
		"""
 | 
			
		||||
		if nickname == "":
 | 
			
		||||
			nickname = self.IRCUsername
 | 
			
		||||
@@ -103,8 +103,7 @@ class Client:
 | 
			
		||||
		"""
 | 
			
		||||
		Add a 403 reply (no such channel) to client buffer.
 | 
			
		||||
 | 
			
		||||
		:param channel:
 | 
			
		||||
		:return:
 | 
			
		||||
		channel -- meh
 | 
			
		||||
		"""
 | 
			
		||||
		self.replyCode(403, "{} :No such channel".format(channel))
 | 
			
		||||
 | 
			
		||||
@@ -113,8 +112,7 @@ class Client:
 | 
			
		||||
		"""
 | 
			
		||||
		Add a 461 reply (not enough parameters) to client buffer
 | 
			
		||||
 | 
			
		||||
		:param command: name of the command that had not enough parameters
 | 
			
		||||
		:return:
 | 
			
		||||
		command -- command that had not enough parameters
 | 
			
		||||
		"""
 | 
			
		||||
		self.replyCode(403, "{} :Not enough parameters".format(command))
 | 
			
		||||
 | 
			
		||||
@@ -123,9 +121,8 @@ class Client:
 | 
			
		||||
		"""
 | 
			
		||||
		Disconnects this client from the IRC server
 | 
			
		||||
 | 
			
		||||
		:param quitmsg: IRC quit message. Default: 'Client quit'
 | 
			
		||||
		:param callLogout: if True, call logoutEvent on bancho
 | 
			
		||||
		:return:
 | 
			
		||||
		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))
 | 
			
		||||
@@ -141,11 +138,7 @@ class Client:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	def readSocket(self):
 | 
			
		||||
		"""
 | 
			
		||||
		Read data coming from this client socket
 | 
			
		||||
 | 
			
		||||
		:return:
 | 
			
		||||
		"""
 | 
			
		||||
		"""Read data coming from this client socket"""
 | 
			
		||||
		try:
 | 
			
		||||
			# Try to read incoming data from socket
 | 
			
		||||
			data = self.socket.recv(2 ** 10)
 | 
			
		||||
@@ -168,11 +161,7 @@ class Client:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	def parseBuffer(self):
 | 
			
		||||
		"""
 | 
			
		||||
		Parse self.__readbuffer, get command, arguments and call its handler
 | 
			
		||||
 | 
			
		||||
		:return:
 | 
			
		||||
		"""
 | 
			
		||||
		"""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]
 | 
			
		||||
@@ -209,11 +198,7 @@ class Client:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	def writeSocket(self):
 | 
			
		||||
		"""
 | 
			
		||||
		Write buffer to socket
 | 
			
		||||
 | 
			
		||||
		:return:
 | 
			
		||||
		"""
 | 
			
		||||
		"""Write buffer to socket"""
 | 
			
		||||
		try:
 | 
			
		||||
			sent = self.socket.send(self.__writebuffer.encode())
 | 
			
		||||
			log.debug("[IRC] [{}:{}] <- {}".format(self.ip, self.port, self.__writebuffer[:sent]))
 | 
			
		||||
@@ -221,13 +206,9 @@ class Client:
 | 
			
		||||
		except socket.error as x:
 | 
			
		||||
			self.disconnect(str(x))
 | 
			
		||||
 | 
			
		||||
	def checkAlive(self):
 | 
			
		||||
		"""
 | 
			
		||||
		Check if this client is still connected.
 | 
			
		||||
		If the client is dead, disconnect it.
 | 
			
		||||
 | 
			
		||||
		:return:
 | 
			
		||||
		"""
 | 
			
		||||
	def checkAlive(self):
 | 
			
		||||
		"""Check if this client is still connected"""
 | 
			
		||||
		now = time.time()
 | 
			
		||||
		if self.__timestamp + 180 < now:
 | 
			
		||||
			self.disconnect("ping timeout")
 | 
			
		||||
@@ -243,19 +224,11 @@ class Client:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	def sendLusers(self):
 | 
			
		||||
		"""
 | 
			
		||||
		Send lusers response to this client
 | 
			
		||||
 | 
			
		||||
		:return:
 | 
			
		||||
		"""
 | 
			
		||||
		"""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
 | 
			
		||||
 | 
			
		||||
		:return:
 | 
			
		||||
		"""
 | 
			
		||||
		"""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")
 | 
			
		||||
@@ -367,13 +340,13 @@ class Client:
 | 
			
		||||
 | 
			
		||||
		# 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'''
 | 
			
		||||
			return
 | 
			
		||||
 | 
			
		||||
		# Get channels to join list
 | 
			
		||||
		channels = arguments[0].split(",")
 | 
			
		||||
@@ -402,7 +375,7 @@ class Client:
 | 
			
		||||
					self.replyCode(332, description, channel=channel)
 | 
			
		||||
 | 
			
		||||
				# Build connected users list
 | 
			
		||||
				users = glob.channels.channels[channel].connectedUsers[:]
 | 
			
		||||
				users = glob.channels.channels[channel].getConnectedUsers()[:]
 | 
			
		||||
				usernames = []
 | 
			
		||||
				for user in users:
 | 
			
		||||
					token = glob.tokens.getTokenFromUserID(user)
 | 
			
		||||
@@ -515,14 +488,11 @@ class Client:
 | 
			
		||||
		pass
 | 
			
		||||
 | 
			
		||||
	def awayHandler(self, command, arguments):
 | 
			
		||||
		"""AWAY command handler"""
 | 
			
		||||
		response = chat.IRCAway(self.banchoUsername, " ".join(arguments))
 | 
			
		||||
		self.replyCode(response, "You are no longer marked as being away" if response == 305 else "You have been marked as being away")
 | 
			
		||||
 | 
			
		||||
	def mainHandler(self, command, arguments):
 | 
			
		||||
		"""
 | 
			
		||||
		Handler for post-login commands
 | 
			
		||||
		"""
 | 
			
		||||
		"""Handler for post-login commands"""
 | 
			
		||||
		handlers = {
 | 
			
		||||
			"AWAY": self.awayHandler,
 | 
			
		||||
			#"ISON": ison_handler,
 | 
			
		||||
@@ -552,18 +522,17 @@ class Client:
 | 
			
		||||
 | 
			
		||||
class Server:
 | 
			
		||||
	def __init__(self, port):
 | 
			
		||||
		#self.host = socket.getfqdn("127.0.0.1")[:63]
 | 
			
		||||
		self.host = glob.conf.config["irc"]["hostname"]
 | 
			
		||||
		self.port = port
 | 
			
		||||
		self.clients = {}  # Socket - - > Client instance.
 | 
			
		||||
		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, isBanchoUsername=True):
 | 
			
		||||
		"""
 | 
			
		||||
		Disconnect someone from IRC if connected
 | 
			
		||||
 | 
			
		||||
		:param username: victim
 | 
			
		||||
		:param isBanchoUsername: if True, username is a bancho username, else convert it to a bancho username
 | 
			
		||||
		:return:
 | 
			
		||||
		username -- victim
 | 
			
		||||
		"""
 | 
			
		||||
		for _, value in self.clients.items():
 | 
			
		||||
			if (value.IRCUsername == username and not isBanchoUsername) or (value.banchoUsername == username and isBanchoUsername):
 | 
			
		||||
@@ -574,9 +543,8 @@ class Server:
 | 
			
		||||
		"""
 | 
			
		||||
		Let every IRC client connected to a specific client know that 'username' joined the channel from bancho
 | 
			
		||||
 | 
			
		||||
		:param username: username of bancho user
 | 
			
		||||
		:param channel: joined channel name
 | 
			
		||||
		:return:
 | 
			
		||||
		username -- username of bancho user
 | 
			
		||||
		channel -- joined channel name
 | 
			
		||||
		"""
 | 
			
		||||
		username = chat.fixUsernameForIRC(username)
 | 
			
		||||
		for _, value in self.clients.items():
 | 
			
		||||
@@ -587,9 +555,8 @@ class Server:
 | 
			
		||||
		"""
 | 
			
		||||
		Let every IRC client connected to a specific client know that 'username' parted the channel from bancho
 | 
			
		||||
 | 
			
		||||
		:param username: username of bancho user
 | 
			
		||||
		:param channel: joined channel name
 | 
			
		||||
		:return:
 | 
			
		||||
		username -- username of bancho user
 | 
			
		||||
		channel -- joined channel name
 | 
			
		||||
		"""
 | 
			
		||||
		username = chat.fixUsernameForIRC(username)
 | 
			
		||||
		for _, value in self.clients.items():
 | 
			
		||||
@@ -600,10 +567,9 @@ class Server:
 | 
			
		||||
		"""
 | 
			
		||||
		Send a message to IRC when someone sends it from bancho
 | 
			
		||||
 | 
			
		||||
		:param fro: sender username
 | 
			
		||||
		:param to: receiver username
 | 
			
		||||
		:param message: text of the message
 | 
			
		||||
		:return:
 | 
			
		||||
		fro -- sender username
 | 
			
		||||
		to -- receiver username
 | 
			
		||||
		message -- text of the message
 | 
			
		||||
		"""
 | 
			
		||||
		fro = chat.fixUsernameForIRC(fro)
 | 
			
		||||
		to = chat.fixUsernameForIRC(to)
 | 
			
		||||
@@ -623,19 +589,14 @@ class Server:
 | 
			
		||||
		"""
 | 
			
		||||
		Remove a client from connected clients
 | 
			
		||||
 | 
			
		||||
		:param client: client object
 | 
			
		||||
		:param quitmsg: QUIT argument, useless atm
 | 
			
		||||
		:return:
 | 
			
		||||
		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
 | 
			
		||||
 | 
			
		||||
		:return:
 | 
			
		||||
		"""
 | 
			
		||||
		"""Start IRC server main loop"""
 | 
			
		||||
		# Sentry
 | 
			
		||||
		if glob.sentry:
 | 
			
		||||
			sentryClient = raven.Client(glob.conf.config["sentry"]["ircdns"])
 | 
			
		||||
@@ -692,11 +653,5 @@ class Server:
 | 
			
		||||
					sentryClient.captureException()
 | 
			
		||||
 | 
			
		||||
def main(port=6667):
 | 
			
		||||
	"""
 | 
			
		||||
	Create and start an IRC server
 | 
			
		||||
 | 
			
		||||
	:param port: IRC port. Default: 6667
 | 
			
		||||
	:return:
 | 
			
		||||
	"""
 | 
			
		||||
	glob.ircServer = Server(port)
 | 
			
		||||
	glob.ircServer.start()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,16 +1,20 @@
 | 
			
		||||
from objects import glob
 | 
			
		||||
 | 
			
		||||
class channel:
 | 
			
		||||
	"""
 | 
			
		||||
	A chat channel
 | 
			
		||||
	"""
 | 
			
		||||
 | 
			
		||||
	def __init__(self, name, description, publicRead, publicWrite, temp, hidden):
 | 
			
		||||
		"""
 | 
			
		||||
		Create a new chat channel object
 | 
			
		||||
 | 
			
		||||
		:param name: channel name
 | 
			
		||||
		:param description: channel description
 | 
			
		||||
		:param publicRead: if True, this channel can be read by everyone. If False, it can be read only by mods/admins
 | 
			
		||||
		:param publicWrite: same as public read, but regards writing permissions
 | 
			
		||||
		:param temp: if True, this channel will be deleted when there's no one in this channel
 | 
			
		||||
		:param hidden: if True, thic channel won't be shown in channels list
 | 
			
		||||
		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
 | 
			
		||||
		hidden -- if True, channel won't be shown in channels list
 | 
			
		||||
		"""
 | 
			
		||||
		self.name = name
 | 
			
		||||
		self.description = description
 | 
			
		||||
@@ -32,8 +36,7 @@ class channel:
 | 
			
		||||
		"""
 | 
			
		||||
		Add a user to connected users
 | 
			
		||||
 | 
			
		||||
		:param userID:
 | 
			
		||||
		:return:
 | 
			
		||||
		userID -- user ID that joined the channel
 | 
			
		||||
		"""
 | 
			
		||||
		if userID not in self.connectedUsers:
 | 
			
		||||
			self.connectedUsers.append(userID)
 | 
			
		||||
@@ -42,8 +45,7 @@ class channel:
 | 
			
		||||
		"""
 | 
			
		||||
		Remove a user from connected users
 | 
			
		||||
 | 
			
		||||
		:param userID:
 | 
			
		||||
		:return:
 | 
			
		||||
		userID -- user ID that left the channel
 | 
			
		||||
		"""
 | 
			
		||||
		if userID in self.connectedUsers:
 | 
			
		||||
			self.connectedUsers.remove(userID)
 | 
			
		||||
@@ -51,4 +53,20 @@ class channel:
 | 
			
		||||
		# 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)
 | 
			
		||||
			glob.channels.removeChannel(self.name)
 | 
			
		||||
 | 
			
		||||
	def getConnectedUsers(self):
 | 
			
		||||
		"""
 | 
			
		||||
		Get connected user IDs list
 | 
			
		||||
 | 
			
		||||
		return -- connectedUsers list
 | 
			
		||||
		"""
 | 
			
		||||
		return self.connectedUsers
 | 
			
		||||
 | 
			
		||||
	def getConnectedUsersCount(self):
 | 
			
		||||
		"""
 | 
			
		||||
		Count connected users
 | 
			
		||||
 | 
			
		||||
		return -- connected users number
 | 
			
		||||
		"""
 | 
			
		||||
		return len(self.connectedUsers)
 | 
			
		||||
 
 | 
			
		||||
@@ -4,14 +4,18 @@ from objects import glob
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class channelList:
 | 
			
		||||
	def __init__(self):
 | 
			
		||||
		self.channels = {}
 | 
			
		||||
	"""
 | 
			
		||||
	Channel list
 | 
			
		||||
 | 
			
		||||
	channels -- dictionary. key: channel name, value: channel object
 | 
			
		||||
	"""
 | 
			
		||||
	channels = {}
 | 
			
		||||
 | 
			
		||||
	def loadChannels(self):
 | 
			
		||||
		"""
 | 
			
		||||
		Load chat channels from db and add them to channels list
 | 
			
		||||
		:return:
 | 
			
		||||
		Load chat channels from db and add them to channels dictionary
 | 
			
		||||
		"""
 | 
			
		||||
 | 
			
		||||
		# Get channels from DB
 | 
			
		||||
		channels = glob.db.fetchAll("SELECT * FROM bancho_channels")
 | 
			
		||||
 | 
			
		||||
@@ -24,15 +28,14 @@ class channelList:
 | 
			
		||||
 | 
			
		||||
	def addChannel(self, name, description, publicRead, publicWrite, temp = False, hidden = False):
 | 
			
		||||
		"""
 | 
			
		||||
		Add a channel to channels list
 | 
			
		||||
		Add a channel object to channels dictionary
 | 
			
		||||
 | 
			
		||||
		:param name: channel name
 | 
			
		||||
		:param description: channel description
 | 
			
		||||
		:param publicRead: if True, this channel can be read by everyone. If False, it can be read only by mods/admins
 | 
			
		||||
		:param publicWrite: same as public read, but regards writing permissions
 | 
			
		||||
		:param temp: if True, this channel will be deleted when there's no one in this channel
 | 
			
		||||
		:param hidden: if True, thic channel won't be shown in channels list
 | 
			
		||||
		:return:
 | 
			
		||||
		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.
 | 
			
		||||
		hidden -- if True, channel will be hidden in channels list. Optional. Default = False.
 | 
			
		||||
		"""
 | 
			
		||||
		self.channels[name] = channel.channel(name, description, publicRead, publicWrite, temp, hidden)
 | 
			
		||||
		log.info("Created channel {}".format(name))
 | 
			
		||||
@@ -42,8 +45,8 @@ class channelList:
 | 
			
		||||
		Add a temporary channel (like #spectator or #multiplayer), gets deleted when there's no one in the channel
 | 
			
		||||
		and it's hidden in channels list
 | 
			
		||||
 | 
			
		||||
		:param name: channel name
 | 
			
		||||
		:return: True if the channel was created, otherwise False
 | 
			
		||||
		name -- channel name
 | 
			
		||||
		return -- True if channel was created, False if failed
 | 
			
		||||
		"""
 | 
			
		||||
		if name in self.channels:
 | 
			
		||||
			return False
 | 
			
		||||
@@ -54,8 +57,7 @@ class channelList:
 | 
			
		||||
		"""
 | 
			
		||||
		Removes a channel from channels list
 | 
			
		||||
 | 
			
		||||
		:param name: channel name
 | 
			
		||||
		:return:
 | 
			
		||||
		name -- channel name
 | 
			
		||||
		"""
 | 
			
		||||
		if name not in self.channels:
 | 
			
		||||
			log.debug("{} is not in channels list".format(name))
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,9 @@
 | 
			
		||||
class chatFilters:
 | 
			
		||||
	def __init__(self, fileName="filters.txt"):
 | 
			
		||||
		"""
 | 
			
		||||
		Initialize chat filters
 | 
			
		||||
 | 
			
		||||
		:param fileName: name of the file containing filters. Default: filters.txt
 | 
			
		||||
		"""
 | 
			
		||||
		self.filters = {}
 | 
			
		||||
		self.loadFilters(fileName)
 | 
			
		||||
 | 
			
		||||
	def loadFilters(self, fileName="filters.txt"):
 | 
			
		||||
		"""
 | 
			
		||||
		Load filters from a file
 | 
			
		||||
 | 
			
		||||
		:param fileName: name of the file containing filters. Default: filters.txt
 | 
			
		||||
		:return:
 | 
			
		||||
		"""
 | 
			
		||||
		# Reset chat filters
 | 
			
		||||
		self.filters = {}
 | 
			
		||||
 | 
			
		||||
@@ -30,12 +19,6 @@ class chatFilters:
 | 
			
		||||
				self.filters[lineSplit[0].lower()] = lineSplit[1].replace("\n", "")
 | 
			
		||||
 | 
			
		||||
	def filterMessage(self, message):
 | 
			
		||||
		"""
 | 
			
		||||
		Replace forbidden words with filtered ones
 | 
			
		||||
 | 
			
		||||
		:param message: normal message
 | 
			
		||||
		:return: filtered message
 | 
			
		||||
		"""
 | 
			
		||||
		# Split words by spaces
 | 
			
		||||
		messageTemp = message.split(" ")
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,35 +12,29 @@ from objects import glob
 | 
			
		||||
npRegex = re.compile("^https?:\\/\\/osu\\.ppy\\.sh\\/b\\/(\\d*)")
 | 
			
		||||
 | 
			
		||||
def connect():
 | 
			
		||||
	"""
 | 
			
		||||
	Connect FokaBot to Bancho
 | 
			
		||||
 | 
			
		||||
	:return:
 | 
			
		||||
	"""
 | 
			
		||||
	"""Add FokaBot to connected users and send userpanel/stats packet to everyone"""
 | 
			
		||||
	token = glob.tokens.addToken(999)
 | 
			
		||||
	token.actionID = actions.IDLE
 | 
			
		||||
	glob.streams.broadcast("main", serverPackets.userPanel(999))
 | 
			
		||||
	glob.streams.broadcast("main", serverPackets.userStats(999))
 | 
			
		||||
 | 
			
		||||
def disconnect():
 | 
			
		||||
	"""
 | 
			
		||||
	Disconnect FokaBot from Bancho
 | 
			
		||||
 | 
			
		||||
	:return:
 | 
			
		||||
	"""
 | 
			
		||||
	"""Remove FokaBot from connected users"""
 | 
			
		||||
	glob.tokens.deleteToken(glob.tokens.getTokenFromUserID(999))
 | 
			
		||||
 | 
			
		||||
def fokabotResponse(fro, chan, message):
 | 
			
		||||
	"""
 | 
			
		||||
	Check if a message has triggered FokaBot
 | 
			
		||||
	Check if a message has triggered fokabot (and return its response)
 | 
			
		||||
 | 
			
		||||
	:param fro: sender username
 | 
			
		||||
	:param chan: channel name (or receiver username)
 | 
			
		||||
	:param message: chat mesage
 | 
			
		||||
	:return: FokaBot's response or False if no response
 | 
			
		||||
	fro -- sender username (for permissions stuff with admin commands)
 | 
			
		||||
	chan -- channel name
 | 
			
		||||
	message -- message
 | 
			
		||||
 | 
			
		||||
	return -- fokabot's response string or False
 | 
			
		||||
	"""
 | 
			
		||||
	for i in fokabotCommands.commands:
 | 
			
		||||
		# Loop though all commands
 | 
			
		||||
		#if i["trigger"] in message:
 | 
			
		||||
		if generalUtils.strContains(message, i["trigger"]):
 | 
			
		||||
			# message has triggered a command
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -13,14 +13,13 @@ try:
 | 
			
		||||
	with open("version") as f:
 | 
			
		||||
		VERSION = f.read()
 | 
			
		||||
	if VERSION == "":
 | 
			
		||||
		raise Exception
 | 
			
		||||
		raise
 | 
			
		||||
except:
 | 
			
		||||
	VERSION = "Unknown"
 | 
			
		||||
	VERSION = "¯\_(xd)_/¯"
 | 
			
		||||
 | 
			
		||||
DATADOG_PREFIX = "peppy"
 | 
			
		||||
application = None
 | 
			
		||||
db = None
 | 
			
		||||
redis = None
 | 
			
		||||
conf = None
 | 
			
		||||
banchoConf = None
 | 
			
		||||
tokens = tokenList.tokenList()
 | 
			
		||||
@@ -32,6 +31,7 @@ schiavo = schiavo.schiavo()
 | 
			
		||||
dog = datadogClient.datadogClient()
 | 
			
		||||
verifiedCache = {}
 | 
			
		||||
chatFilters = None
 | 
			
		||||
userIDCache = {}
 | 
			
		||||
pool = None
 | 
			
		||||
ircServer = None
 | 
			
		||||
busyThreads = 0
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										192
									
								
								objects/match.py
									
									
									
									
									
								
							
							
						
						
									
										192
									
								
								objects/match.py
									
									
									
									
									
								
							@@ -13,7 +13,7 @@ from objects import glob
 | 
			
		||||
 | 
			
		||||
class slot:
 | 
			
		||||
	def __init__(self):
 | 
			
		||||
		self.status = slotStatuses.FREE
 | 
			
		||||
		self.status = slotStatuses.free
 | 
			
		||||
		self.team = 0
 | 
			
		||||
		self.userID = -1
 | 
			
		||||
		self.user = None
 | 
			
		||||
@@ -23,18 +23,19 @@ class slot:
 | 
			
		||||
		self.complete = False
 | 
			
		||||
 | 
			
		||||
class match:
 | 
			
		||||
	"""Multiplayer match object"""
 | 
			
		||||
	def __init__(self, matchID, matchName, matchPassword, beatmapID, beatmapName, beatmapMD5, gameMode, hostUserID):
 | 
			
		||||
		"""
 | 
			
		||||
		Create a new match object
 | 
			
		||||
 | 
			
		||||
		:param matchID: match progressive identifier
 | 
			
		||||
		:param matchName: match name, string
 | 
			
		||||
		:param matchPassword: match md5 password. Leave empty for no password
 | 
			
		||||
		:param beatmapID: beatmap ID
 | 
			
		||||
		:param beatmapName: beatmap name, string
 | 
			
		||||
		:param beatmapMD5: beatmap md5 hash, string
 | 
			
		||||
		:param gameMode: game mode ID. See gameModes.py
 | 
			
		||||
		:param hostUserID: user id of the host
 | 
			
		||||
		matchID -- match progressive identifier
 | 
			
		||||
		matchName -- match name, string
 | 
			
		||||
		matchPassword -- match md5 password. Leave empty for no password
 | 
			
		||||
		beatmapID -- beatmap ID
 | 
			
		||||
		beatmapName -- beatmap name, string
 | 
			
		||||
		beatmapMD5 -- beatmap md5 hash, string
 | 
			
		||||
		gameMode -- game mode ID. See gameModes.py
 | 
			
		||||
		hostUserID -- user id of the host
 | 
			
		||||
		"""
 | 
			
		||||
		self.matchID = matchID
 | 
			
		||||
		self.streamName = "multi/{}".format(self.matchID)
 | 
			
		||||
@@ -48,9 +49,9 @@ class match:
 | 
			
		||||
		self.beatmapMD5 = beatmapMD5
 | 
			
		||||
		self.hostUserID = hostUserID
 | 
			
		||||
		self.gameMode = gameMode
 | 
			
		||||
		self.matchScoringType = matchScoringTypes.SCORE	# default values
 | 
			
		||||
		self.matchTeamType = matchTeamTypes.HEAD_TO_HEAD		# default value
 | 
			
		||||
		self.matchModMode = matchModModes.NORMAL			# default value
 | 
			
		||||
		self.matchScoringType = matchScoringTypes.score	# default values
 | 
			
		||||
		self.matchTeamType = matchTeamTypes.headToHead		# default value
 | 
			
		||||
		self.matchModMode = matchModModes.normal			# default value
 | 
			
		||||
		self.seed = 0
 | 
			
		||||
		self.matchDataCache = bytes()
 | 
			
		||||
 | 
			
		||||
@@ -69,8 +70,6 @@ class match:
 | 
			
		||||
	def getMatchData(self):
 | 
			
		||||
		"""
 | 
			
		||||
		Return binary match data structure for packetHelper
 | 
			
		||||
 | 
			
		||||
		:return:
 | 
			
		||||
		"""
 | 
			
		||||
		# General match info
 | 
			
		||||
		# TODO: Test without safe copy, the error might have been caused by outdated python bytecode cache
 | 
			
		||||
@@ -110,7 +109,7 @@ class match:
 | 
			
		||||
		])
 | 
			
		||||
 | 
			
		||||
		# Slot mods if free mod is enabled
 | 
			
		||||
		if safeMatch.matchModMode == matchModModes.FREE_MOD:
 | 
			
		||||
		if safeMatch.matchModMode == matchModModes.freeMod:
 | 
			
		||||
			for i in range(0,16):
 | 
			
		||||
				struct.append([safeMatch.slots[i].mods, dataTypes.UINT32])
 | 
			
		||||
 | 
			
		||||
@@ -124,8 +123,7 @@ class match:
 | 
			
		||||
		"""
 | 
			
		||||
		Set room host to newHost and send him host packet
 | 
			
		||||
 | 
			
		||||
		:param newHost: new host userID
 | 
			
		||||
		:return:
 | 
			
		||||
		newHost -- new host userID
 | 
			
		||||
		"""
 | 
			
		||||
		slotID = self.getUserSlotID(newHost)
 | 
			
		||||
		if slotID is None or self.slots[slotID].user not in glob.tokens.tokens:
 | 
			
		||||
@@ -137,21 +135,7 @@ class match:
 | 
			
		||||
		log.info("MPROOM{}: {} is now the host".format(self.matchID, token.username))
 | 
			
		||||
 | 
			
		||||
	def setSlot(self, slotID, status = None, team = None, user = "", mods = None, loaded = None, skip = None, complete = None):
 | 
			
		||||
		"""
 | 
			
		||||
		Set data for a specific slot.
 | 
			
		||||
		All fields but slotID are optional.
 | 
			
		||||
		Skipped fields won't be edited.
 | 
			
		||||
 | 
			
		||||
		:param slotID: slot ID
 | 
			
		||||
		:param status: new status
 | 
			
		||||
		:param team: new team
 | 
			
		||||
		:param user: new user id
 | 
			
		||||
		:param mods: new mods
 | 
			
		||||
		:param loaded: new loaded status
 | 
			
		||||
		:param skip: new skip value
 | 
			
		||||
		:param complete: new completed value
 | 
			
		||||
		:return:
 | 
			
		||||
		"""
 | 
			
		||||
		#self.setSlot(i, slotStatuses.notReady, 0, user, 0)
 | 
			
		||||
		if status is not None:
 | 
			
		||||
			self.slots[slotID].status = status
 | 
			
		||||
 | 
			
		||||
@@ -177,9 +161,8 @@ class match:
 | 
			
		||||
		"""
 | 
			
		||||
		Set slotID mods. Same as calling setSlot and then sendUpdate
 | 
			
		||||
 | 
			
		||||
		:param slotID: slot number
 | 
			
		||||
		:param mods: new mods
 | 
			
		||||
		:return:
 | 
			
		||||
		slotID -- slot number
 | 
			
		||||
		mods -- new mods
 | 
			
		||||
		"""
 | 
			
		||||
		# Set new slot data and send update
 | 
			
		||||
		self.setSlot(slotID, mods=mods)
 | 
			
		||||
@@ -191,15 +174,14 @@ class match:
 | 
			
		||||
		Switch slotID ready/not ready status
 | 
			
		||||
		Same as calling setSlot and then sendUpdate
 | 
			
		||||
 | 
			
		||||
		:param slotID: slot number
 | 
			
		||||
		:return:
 | 
			
		||||
		slotID -- slot number
 | 
			
		||||
		"""
 | 
			
		||||
		# Update ready status and setnd update
 | 
			
		||||
		oldStatus = self.slots[slotID].status
 | 
			
		||||
		if oldStatus == slotStatuses.READY:
 | 
			
		||||
			newStatus = slotStatuses.NOT_READY
 | 
			
		||||
		if oldStatus == slotStatuses.ready:
 | 
			
		||||
			newStatus = slotStatuses.notReady
 | 
			
		||||
		else:
 | 
			
		||||
			newStatus = slotStatuses.READY
 | 
			
		||||
			newStatus = slotStatuses.ready
 | 
			
		||||
		self.setSlot(slotID, newStatus)
 | 
			
		||||
		self.sendUpdates()
 | 
			
		||||
		log.info("MPROOM{}: Slot{} changed ready status to {}".format(self.matchID, slotID, self.slots[slotID].status))
 | 
			
		||||
@@ -209,14 +191,13 @@ class match:
 | 
			
		||||
		Lock a slot
 | 
			
		||||
		Same as calling setSlot and then sendUpdate
 | 
			
		||||
 | 
			
		||||
		:param slotID: slot number
 | 
			
		||||
		:return:
 | 
			
		||||
		slotID -- slot number
 | 
			
		||||
		"""
 | 
			
		||||
		# Check if slot is already locked
 | 
			
		||||
		if self.slots[slotID].status == slotStatuses.LOCKED:
 | 
			
		||||
			newStatus = slotStatuses.FREE
 | 
			
		||||
		if self.slots[slotID].status == slotStatuses.locked:
 | 
			
		||||
			newStatus = slotStatuses.free
 | 
			
		||||
		else:
 | 
			
		||||
			newStatus = slotStatuses.LOCKED
 | 
			
		||||
			newStatus = slotStatuses.locked
 | 
			
		||||
 | 
			
		||||
		# Send updated settings to kicked user, so he returns to lobby
 | 
			
		||||
		if self.slots[slotID].user is not None and self.slots[slotID].user in glob.tokens.tokens:
 | 
			
		||||
@@ -227,14 +208,13 @@ class match:
 | 
			
		||||
 | 
			
		||||
		# Send updates to everyone else
 | 
			
		||||
		self.sendUpdates()
 | 
			
		||||
		log.info("MPROOM{}: Slot{} {}".format(self.matchID, slotID, "locked" if newStatus == slotStatuses.LOCKED else "unlocked"))
 | 
			
		||||
		log.info("MPROOM{}: Slot{} {}".format(self.matchID, slotID, "locked" if newStatus == slotStatuses.locked else "unlocked"))
 | 
			
		||||
 | 
			
		||||
	def playerLoaded(self, userID):
 | 
			
		||||
		"""
 | 
			
		||||
		Set a player loaded status to True
 | 
			
		||||
 | 
			
		||||
		:param userID: ID of user
 | 
			
		||||
		:return:
 | 
			
		||||
		userID -- ID of user
 | 
			
		||||
		"""
 | 
			
		||||
		slotID = self.getUserSlotID(userID)
 | 
			
		||||
		if slotID is None:
 | 
			
		||||
@@ -248,7 +228,7 @@ class match:
 | 
			
		||||
		total = 0
 | 
			
		||||
		loaded = 0
 | 
			
		||||
		for i in range(0,16):
 | 
			
		||||
			if self.slots[i].status == slotStatuses.PLAYING:
 | 
			
		||||
			if self.slots[i].status == slotStatuses.playing:
 | 
			
		||||
				total+=1
 | 
			
		||||
				if self.slots[i].loaded:
 | 
			
		||||
					loaded+=1
 | 
			
		||||
@@ -257,11 +237,7 @@ class match:
 | 
			
		||||
			self.allPlayersLoaded()
 | 
			
		||||
 | 
			
		||||
	def allPlayersLoaded(self):
 | 
			
		||||
		"""
 | 
			
		||||
		Send allPlayersLoaded packet to every playing usr in match
 | 
			
		||||
 | 
			
		||||
		:return:
 | 
			
		||||
		"""
 | 
			
		||||
		"""Send allPlayersLoaded packet to every playing usr in match"""
 | 
			
		||||
		glob.streams.broadcast(self.playingStreamName, serverPackets.allPlayersLoaded())
 | 
			
		||||
		log.info("MPROOM{}: All players loaded! Match starting...".format(self.matchID))
 | 
			
		||||
 | 
			
		||||
@@ -269,8 +245,7 @@ class match:
 | 
			
		||||
		"""
 | 
			
		||||
		Set a player skip status to True
 | 
			
		||||
 | 
			
		||||
		:param userID: ID of user
 | 
			
		||||
		:return:
 | 
			
		||||
		userID -- ID of user
 | 
			
		||||
		"""
 | 
			
		||||
		slotID = self.getUserSlotID(userID)
 | 
			
		||||
		if slotID is None:
 | 
			
		||||
@@ -288,7 +263,7 @@ class match:
 | 
			
		||||
		total = 0
 | 
			
		||||
		skipped = 0
 | 
			
		||||
		for i in range(0,16):
 | 
			
		||||
			if self.slots[i].status == slotStatuses.PLAYING:
 | 
			
		||||
			if self.slots[i].status == slotStatuses.playing:
 | 
			
		||||
				total+=1
 | 
			
		||||
				if self.slots[i].skip:
 | 
			
		||||
					skipped+=1
 | 
			
		||||
@@ -297,11 +272,7 @@ class match:
 | 
			
		||||
			self.allPlayersSkipped()
 | 
			
		||||
 | 
			
		||||
	def allPlayersSkipped(self):
 | 
			
		||||
		"""
 | 
			
		||||
		Send allPlayersSkipped packet to every playing usr in match
 | 
			
		||||
 | 
			
		||||
		:return:
 | 
			
		||||
		"""
 | 
			
		||||
		"""Send allPlayersSkipped packet to every playing usr in match"""
 | 
			
		||||
		glob.streams.broadcast(self.playingStreamName, serverPackets.allPlayersSkipped())
 | 
			
		||||
		log.info("MPROOM{}: All players have skipped!".format(self.matchID))
 | 
			
		||||
 | 
			
		||||
@@ -309,7 +280,7 @@ class match:
 | 
			
		||||
		"""
 | 
			
		||||
		Set userID's slot completed to True
 | 
			
		||||
 | 
			
		||||
		:param userID: ID of user
 | 
			
		||||
		userID -- ID of user
 | 
			
		||||
		"""
 | 
			
		||||
		slotID = self.getUserSlotID(userID)
 | 
			
		||||
		if slotID is None:
 | 
			
		||||
@@ -323,7 +294,7 @@ class match:
 | 
			
		||||
		total = 0
 | 
			
		||||
		completed = 0
 | 
			
		||||
		for i in range(0,16):
 | 
			
		||||
			if self.slots[i].status == slotStatuses.PLAYING:
 | 
			
		||||
			if self.slots[i].status == slotStatuses.playing:
 | 
			
		||||
				total+=1
 | 
			
		||||
				if self.slots[i].complete:
 | 
			
		||||
					completed+=1
 | 
			
		||||
@@ -332,18 +303,15 @@ class match:
 | 
			
		||||
			self.allPlayersCompleted()
 | 
			
		||||
 | 
			
		||||
	def allPlayersCompleted(self):
 | 
			
		||||
		"""
 | 
			
		||||
		Cleanup match stuff and send match end packet to everyone
 | 
			
		||||
		"""Cleanup match stuff and send match end packet to everyone"""
 | 
			
		||||
 | 
			
		||||
		:return:
 | 
			
		||||
		"""
 | 
			
		||||
		# Reset inProgress
 | 
			
		||||
		self.inProgress = False
 | 
			
		||||
 | 
			
		||||
		# Reset slots
 | 
			
		||||
		for i in range(0,16):
 | 
			
		||||
			if self.slots[i].user is not None and self.slots[i].status == slotStatuses.PLAYING:
 | 
			
		||||
				self.slots[i].status = slotStatuses.NOT_READY
 | 
			
		||||
			if self.slots[i].user is not None and self.slots[i].status == slotStatuses.playing:
 | 
			
		||||
				self.slots[i].status = slotStatuses.notReady
 | 
			
		||||
				self.slots[i].loaded = False
 | 
			
		||||
				self.slots[i].skip = False
 | 
			
		||||
				self.slots[i].complete = False
 | 
			
		||||
@@ -364,7 +332,7 @@ class match:
 | 
			
		||||
		"""
 | 
			
		||||
		Get slot ID occupied by userID
 | 
			
		||||
 | 
			
		||||
		:return: slot id if found, None if user is not in room
 | 
			
		||||
		return -- slot id if found, None if user is not in room
 | 
			
		||||
		"""
 | 
			
		||||
		for i in range(0,16):
 | 
			
		||||
			if self.slots[i].user is not None and self.slots[i].user in glob.tokens.tokens and glob.tokens.tokens[self.slots[i].user].userID == userID:
 | 
			
		||||
@@ -375,20 +343,21 @@ class match:
 | 
			
		||||
		"""
 | 
			
		||||
		Add someone to users in match
 | 
			
		||||
 | 
			
		||||
		:param userID: user id of the user
 | 
			
		||||
		:return: True if join success, False if fail (room is full)
 | 
			
		||||
		userID -- user id of the user
 | 
			
		||||
		return -- True if join success, False if fail (room is full)
 | 
			
		||||
		"""
 | 
			
		||||
 | 
			
		||||
		# Make sure we're not in this match
 | 
			
		||||
		for i in range(0,16):
 | 
			
		||||
			if self.slots[i].user == user.token:
 | 
			
		||||
				# Set bugged slot to free
 | 
			
		||||
				self.setSlot(i, slotStatuses.FREE, 0, None, 0)
 | 
			
		||||
				self.setSlot(i, slotStatuses.free, 0, None, 0)
 | 
			
		||||
 | 
			
		||||
		# Find first free slot
 | 
			
		||||
		for i in range(0,16):
 | 
			
		||||
			if self.slots[i].status == slotStatuses.FREE:
 | 
			
		||||
			if self.slots[i].status == slotStatuses.free:
 | 
			
		||||
				# Occupy slot
 | 
			
		||||
				self.setSlot(i, slotStatuses.NOT_READY, 0, user.token, 0)
 | 
			
		||||
				self.setSlot(i, slotStatuses.notReady, 0, user.token, 0)
 | 
			
		||||
 | 
			
		||||
				# Send updated match data
 | 
			
		||||
				self.sendUpdates()
 | 
			
		||||
@@ -403,8 +372,7 @@ class match:
 | 
			
		||||
		"""
 | 
			
		||||
		Remove someone from users in match
 | 
			
		||||
 | 
			
		||||
		:param userID: user if of the user
 | 
			
		||||
		:return:
 | 
			
		||||
		userID -- user if of the user
 | 
			
		||||
		"""
 | 
			
		||||
		# Make sure the user is in room
 | 
			
		||||
		slotID = self.getUserSlotID(user.userID)
 | 
			
		||||
@@ -412,7 +380,7 @@ class match:
 | 
			
		||||
			return
 | 
			
		||||
 | 
			
		||||
		# Set that slot to free
 | 
			
		||||
		self.setSlot(slotID, slotStatuses.FREE, 0, None, 0)
 | 
			
		||||
		self.setSlot(slotID, slotStatuses.free, 0, None, 0)
 | 
			
		||||
 | 
			
		||||
		# Check if everyone left
 | 
			
		||||
		if self.countUsers() == 0:
 | 
			
		||||
@@ -439,9 +407,8 @@ class match:
 | 
			
		||||
		"""
 | 
			
		||||
		Change userID slot to newSlotID
 | 
			
		||||
 | 
			
		||||
		:param userID: user that changed slot
 | 
			
		||||
		:param newSlotID: slot id of new slot
 | 
			
		||||
		:return:
 | 
			
		||||
		userID -- user that changed slot
 | 
			
		||||
		newSlotID -- slot id of new slot
 | 
			
		||||
		"""
 | 
			
		||||
		# Make sure the user is in room
 | 
			
		||||
		oldSlotID = self.getUserSlotID(userID)
 | 
			
		||||
@@ -449,7 +416,7 @@ class match:
 | 
			
		||||
			return
 | 
			
		||||
 | 
			
		||||
		# Make sure there is no one inside new slot
 | 
			
		||||
		if self.slots[newSlotID].user is not None and self.slots[newSlotID].status != slotStatuses.FREE:
 | 
			
		||||
		if self.slots[newSlotID].user is not None and self.slots[newSlotID].status != slotStatuses.free:
 | 
			
		||||
			return
 | 
			
		||||
 | 
			
		||||
		# Get old slot data
 | 
			
		||||
@@ -457,7 +424,7 @@ class match:
 | 
			
		||||
		oldData = copy.deepcopy(self.slots[oldSlotID])
 | 
			
		||||
 | 
			
		||||
		# Free old slot
 | 
			
		||||
		self.setSlot(oldSlotID, slotStatuses.FREE, 0, None, 0, False, False, False)
 | 
			
		||||
		self.setSlot(oldSlotID, slotStatuses.free, 0, None, 0, False, False, False)
 | 
			
		||||
 | 
			
		||||
		# Occupy new slot
 | 
			
		||||
		self.setSlot(newSlotID, oldData.status, oldData.team, oldData.user, oldData.mods)
 | 
			
		||||
@@ -472,10 +439,13 @@ class match:
 | 
			
		||||
		"""
 | 
			
		||||
		Change match password to newPassword
 | 
			
		||||
 | 
			
		||||
		:param newPassword: new password string
 | 
			
		||||
		:return:
 | 
			
		||||
		newPassword -- new password string
 | 
			
		||||
		"""
 | 
			
		||||
		self.matchPassword = newPassword
 | 
			
		||||
		#if newPassword != "":
 | 
			
		||||
		#	self.matchPassword = generalUtils.stringMd5(newPassword)
 | 
			
		||||
		#else:
 | 
			
		||||
		#	self.matchPassword = ""
 | 
			
		||||
 | 
			
		||||
		# Send password change to every user in match
 | 
			
		||||
		glob.streams.broadcast(self.streamName, serverPackets.changeMatchPassword(self.matchPassword))
 | 
			
		||||
@@ -490,8 +460,7 @@ class match:
 | 
			
		||||
		"""
 | 
			
		||||
		Set match global mods
 | 
			
		||||
 | 
			
		||||
		:param mods: mods bitwise int thing
 | 
			
		||||
		:return:
 | 
			
		||||
		mods -- mods bitwise int thing
 | 
			
		||||
		"""
 | 
			
		||||
		# Set new mods and send update
 | 
			
		||||
		self.mods = mods
 | 
			
		||||
@@ -502,9 +471,8 @@ class match:
 | 
			
		||||
		"""
 | 
			
		||||
		Set no beatmap status for userID
 | 
			
		||||
 | 
			
		||||
		:param userID: ID of user
 | 
			
		||||
		:param has: True if has beatmap, false if not
 | 
			
		||||
		:return:
 | 
			
		||||
		userID -- ID of user
 | 
			
		||||
		has -- True if has beatmap, false if not
 | 
			
		||||
		"""
 | 
			
		||||
		# Make sure the user is in room
 | 
			
		||||
		slotID = self.getUserSlotID(userID)
 | 
			
		||||
@@ -512,7 +480,7 @@ class match:
 | 
			
		||||
			return
 | 
			
		||||
 | 
			
		||||
		# Set slot
 | 
			
		||||
		self.setSlot(slotID, slotStatuses.NO_MAP if not has else slotStatuses.NOT_READY)
 | 
			
		||||
		self.setSlot(slotID, slotStatuses.noMap if not has else slotStatuses.notReady)
 | 
			
		||||
 | 
			
		||||
		# Send updates
 | 
			
		||||
		self.sendUpdates()
 | 
			
		||||
@@ -521,8 +489,7 @@ class match:
 | 
			
		||||
		"""
 | 
			
		||||
		Transfer host to slotID
 | 
			
		||||
 | 
			
		||||
		:param slotID: ID of slot
 | 
			
		||||
		:return:
 | 
			
		||||
		slotID -- ID of slot
 | 
			
		||||
		"""
 | 
			
		||||
		# Make sure there is someone in that slot
 | 
			
		||||
		if self.slots[slotID].user is None or self.slots[slotID].user not in glob.tokens.tokens:
 | 
			
		||||
@@ -538,8 +505,7 @@ class match:
 | 
			
		||||
		"""
 | 
			
		||||
		Send userID's failed packet to everyone in match
 | 
			
		||||
 | 
			
		||||
		:param userID: ID of user
 | 
			
		||||
		:return:
 | 
			
		||||
		userID -- ID of user
 | 
			
		||||
		"""
 | 
			
		||||
		# Make sure the user is in room
 | 
			
		||||
		slotID = self.getUserSlotID(userID)
 | 
			
		||||
@@ -556,10 +522,10 @@ class match:
 | 
			
		||||
		"""
 | 
			
		||||
		Fro invites to in this match.
 | 
			
		||||
 | 
			
		||||
		:param fro: sender userID
 | 
			
		||||
		:param to: receiver userID
 | 
			
		||||
		:return:
 | 
			
		||||
		fro -- sender userID
 | 
			
		||||
		to -- receiver userID
 | 
			
		||||
		"""
 | 
			
		||||
 | 
			
		||||
		# Get tokens
 | 
			
		||||
		froToken = glob.tokens.getTokenFromUserID(fro)
 | 
			
		||||
		toToken = glob.tokens.getTokenFromUserID(to)
 | 
			
		||||
@@ -578,7 +544,7 @@ class match:
 | 
			
		||||
		"""
 | 
			
		||||
		Return how many players are in that match
 | 
			
		||||
 | 
			
		||||
		:return: number of users
 | 
			
		||||
		return -- number of users
 | 
			
		||||
		"""
 | 
			
		||||
		c = 0
 | 
			
		||||
		for i in range(0,16):
 | 
			
		||||
@@ -590,8 +556,7 @@ class match:
 | 
			
		||||
		"""
 | 
			
		||||
		Change userID's team
 | 
			
		||||
 | 
			
		||||
		:param userID: id of user
 | 
			
		||||
		:return:
 | 
			
		||||
		userID -- id of user
 | 
			
		||||
		"""
 | 
			
		||||
		# Make sure the user is in room
 | 
			
		||||
		slotID = self.getUserSlotID(userID)
 | 
			
		||||
@@ -599,16 +564,11 @@ class match:
 | 
			
		||||
			return
 | 
			
		||||
 | 
			
		||||
		# Update slot and send update
 | 
			
		||||
		newTeam = matchTeams.BLUE if self.slots[slotID].team == matchTeams.RED else matchTeams.RED
 | 
			
		||||
		newTeam = matchTeams.blue if self.slots[slotID].team == matchTeams.red else matchTeams.red
 | 
			
		||||
		self.setSlot(slotID, None, newTeam)
 | 
			
		||||
		self.sendUpdates()
 | 
			
		||||
 | 
			
		||||
	def sendUpdates(self):
 | 
			
		||||
		"""
 | 
			
		||||
		Send match updates packet to everyone in lobby and room streams
 | 
			
		||||
 | 
			
		||||
		:return:
 | 
			
		||||
		"""
 | 
			
		||||
		self.matchDataCache = serverPackets.updateMatch(self.matchID)
 | 
			
		||||
		if self.matchDataCache is not None:
 | 
			
		||||
			glob.streams.broadcast(self.streamName, self.matchDataCache)
 | 
			
		||||
@@ -620,17 +580,16 @@ class match:
 | 
			
		||||
		"""
 | 
			
		||||
		Check if match teams are valid
 | 
			
		||||
 | 
			
		||||
		:return: True if valid, False if invalid
 | 
			
		||||
		:return:
 | 
			
		||||
		return -- True if valid, False if invalid
 | 
			
		||||
		"""
 | 
			
		||||
		if self.matchTeamType != matchTeamTypes.TEAM_VS or self.matchTeamType != matchTeamTypes.TAG_TEAM_VS:
 | 
			
		||||
		if self.matchTeamType != matchTeamTypes.teamVs or self.matchTeamType != matchTeamTypes.tagTeamVs:
 | 
			
		||||
			# Teams are always valid if we have no teams
 | 
			
		||||
			return True
 | 
			
		||||
 | 
			
		||||
		# We have teams, check if they are valid
 | 
			
		||||
		firstTeam = -1
 | 
			
		||||
		for i in range(0,16):
 | 
			
		||||
			if self.slots[i].user is not None and (self.slots[i].status & slotStatuses.NO_MAP) == 0:
 | 
			
		||||
			if self.slots[i].user is not None and (self.slots[i].status & slotStatuses.noMap) == 0:
 | 
			
		||||
				if firstTeam == -1:
 | 
			
		||||
					firstTeam = self.slots[i].team
 | 
			
		||||
				elif firstTeam != self.slots[i].team:
 | 
			
		||||
@@ -641,11 +600,6 @@ class match:
 | 
			
		||||
		return False
 | 
			
		||||
 | 
			
		||||
	def start(self):
 | 
			
		||||
		"""
 | 
			
		||||
		Start the match
 | 
			
		||||
 | 
			
		||||
		:return:
 | 
			
		||||
		"""
 | 
			
		||||
		# Make sure we have enough players
 | 
			
		||||
		if self.countUsers() < 2 or not self.checkTeams():
 | 
			
		||||
			return
 | 
			
		||||
@@ -659,8 +613,8 @@ class match:
 | 
			
		||||
		# Set playing to ready players and set load, skip and complete to False
 | 
			
		||||
		# Make clients join playing stream
 | 
			
		||||
		for i in range(0, 16):
 | 
			
		||||
			if (self.slots[i].status & slotStatuses.READY) > 0 and self.slots[i].user in glob.tokens.tokens:
 | 
			
		||||
				self.slots[i].status = slotStatuses.PLAYING
 | 
			
		||||
			if (self.slots[i].status & slotStatuses.ready) > 0 and self.slots[i].user in glob.tokens.tokens:
 | 
			
		||||
				self.slots[i].status = slotStatuses.playing
 | 
			
		||||
				self.slots[i].loaded = False
 | 
			
		||||
				self.slots[i].skip = False
 | 
			
		||||
				self.slots[i].complete = False
 | 
			
		||||
 
 | 
			
		||||
@@ -13,14 +13,14 @@ class matchList:
 | 
			
		||||
		"""
 | 
			
		||||
		Add a new match to matches list
 | 
			
		||||
 | 
			
		||||
		:param matchName: match name, string
 | 
			
		||||
		:param matchPassword: match md5 password. Leave empty for no password
 | 
			
		||||
		:param beatmapID: beatmap ID
 | 
			
		||||
		:param beatmapName: beatmap name, string
 | 
			
		||||
		:param beatmapMD5: beatmap md5 hash, string
 | 
			
		||||
		:param gameMode: game mode ID. See gameModes.py
 | 
			
		||||
		:param hostUserID: user id of who created the match
 | 
			
		||||
		:return: match ID
 | 
			
		||||
		matchName -- match name, string
 | 
			
		||||
		matchPassword -- match md5 password. Leave empty for no password
 | 
			
		||||
		beatmapID -- beatmap ID
 | 
			
		||||
		beatmapName -- beatmap name, string
 | 
			
		||||
		beatmapMD5 -- beatmap md5 hash, string
 | 
			
		||||
		gameMode -- game mode ID. See gameModes.py
 | 
			
		||||
		hostUserID -- user id of who created the match
 | 
			
		||||
		return -- match ID
 | 
			
		||||
		"""
 | 
			
		||||
		# Add a new match to matches list and create its stream
 | 
			
		||||
		matchID = self.lastID
 | 
			
		||||
@@ -32,8 +32,7 @@ class matchList:
 | 
			
		||||
		"""
 | 
			
		||||
		Destroy match object with id = matchID
 | 
			
		||||
 | 
			
		||||
		:param matchID: ID of match to dispose
 | 
			
		||||
		:return:
 | 
			
		||||
		matchID -- ID of match to dispose
 | 
			
		||||
		"""
 | 
			
		||||
		# Make sure the match exists
 | 
			
		||||
		if matchID not in self.matches:
 | 
			
		||||
 
 | 
			
		||||
@@ -12,26 +12,25 @@ from objects import glob
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class token:
 | 
			
		||||
 | 
			
		||||
	def __init__(self, userID, token_ = None, ip ="", irc = False, timeOffset = 0, tournament = False):
 | 
			
		||||
		"""
 | 
			
		||||
		Create a token object and set userID and token
 | 
			
		||||
 | 
			
		||||
		:param userID: user associated to this token
 | 
			
		||||
		:param token_: 	if passed, set token to that value
 | 
			
		||||
						if not passed, token will be generated
 | 
			
		||||
		:param ip: client ip. optional.
 | 
			
		||||
		:param irc: if True, set this token as IRC client. Default: False.
 | 
			
		||||
		:param timeOffset: the time offset from UTC for this user. Default: 0.
 | 
			
		||||
		:param tournament: if True, flag this client as a tournement client. Default: True.
 | 
			
		||||
		userID -- user associated to this 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.
 | 
			
		||||
		timeOffset -- the time offset from UTC for this user. optional.
 | 
			
		||||
		"""
 | 
			
		||||
		# Set stuff
 | 
			
		||||
		self.userID = userID
 | 
			
		||||
		self.username = userUtils.getUsername(self.userID)
 | 
			
		||||
		self.safeUsername = userUtils.getSafeUsername(self.userID)
 | 
			
		||||
		self.privileges = userUtils.getPrivileges(self.userID)
 | 
			
		||||
		self.admin = userUtils.isInPrivilegeGroup(self.userID, "developer") or userUtils.isInPrivilegeGroup(self.userID, "community manager")
 | 
			
		||||
		self.irc = irc
 | 
			
		||||
		self.restricted = userUtils.isRestricted(self.userID)
 | 
			
		||||
		self.restricted = userUtils.isRestricted(priv=self.privileges)
 | 
			
		||||
		self.loginTime = int(time.time())
 | 
			
		||||
		self.pingTime = self.loginTime
 | 
			
		||||
		self.timeOffset = timeOffset
 | 
			
		||||
@@ -95,23 +94,25 @@ class token:
 | 
			
		||||
		"""
 | 
			
		||||
		Add bytes (packets) to queue
 | 
			
		||||
 | 
			
		||||
		:param bytes: (packet) bytes to enqueue
 | 
			
		||||
		bytes -- (packet) bytes to enqueue
 | 
			
		||||
		"""
 | 
			
		||||
		# TODO: reduce max queue size
 | 
			
		||||
		if len(bytes_) < 10 * 10 ** 6:
 | 
			
		||||
			self.queue += bytes_
 | 
			
		||||
		else:
 | 
			
		||||
			log.warning("{}'s packets buffer is above 10M!! Lost some data!".format(self.username))
 | 
			
		||||
		if not self.irc:
 | 
			
		||||
			if len(bytes_) < 10 * 10 ** 6:
 | 
			
		||||
				self.queue += bytes_
 | 
			
		||||
			else:
 | 
			
		||||
				log.warning("{}'s packets buffer is above 10M!! Lost some data!".format(self.username))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	def resetQueue(self):
 | 
			
		||||
		"""Resets the queue. Call when enqueued packets have been sent"""
 | 
			
		||||
		self.queue = bytes()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	def joinChannel(self, channel):
 | 
			
		||||
		"""
 | 
			
		||||
		Add channel to joined channels list
 | 
			
		||||
 | 
			
		||||
		:param channel: channel name
 | 
			
		||||
		channel -- channel name
 | 
			
		||||
		"""
 | 
			
		||||
		if channel not in self.joinedChannels:
 | 
			
		||||
			self.joinedChannels.append(channel)
 | 
			
		||||
@@ -120,24 +121,24 @@ class token:
 | 
			
		||||
		"""
 | 
			
		||||
		Remove channel from joined channels list
 | 
			
		||||
 | 
			
		||||
		:param channel: channel name
 | 
			
		||||
		channel -- channel name
 | 
			
		||||
		"""
 | 
			
		||||
		if channel in self.joinedChannels:
 | 
			
		||||
			self.joinedChannels.remove(channel)
 | 
			
		||||
 | 
			
		||||
	def setLocation(self, latitude, longitude):
 | 
			
		||||
	def setLocation(self, location):
 | 
			
		||||
		"""
 | 
			
		||||
		Set client location
 | 
			
		||||
		Set location (latitude and longitude)
 | 
			
		||||
 | 
			
		||||
		:param location: [latitude, longitude]
 | 
			
		||||
		location -- [latitude, longitude]
 | 
			
		||||
		"""
 | 
			
		||||
		self.location = (latitude, longitude)
 | 
			
		||||
		self.location = location
 | 
			
		||||
 | 
			
		||||
	def getLatitude(self):
 | 
			
		||||
		"""
 | 
			
		||||
		Get latitude
 | 
			
		||||
 | 
			
		||||
		:return: latitude
 | 
			
		||||
		return -- latitude
 | 
			
		||||
		"""
 | 
			
		||||
		return self.location[0]
 | 
			
		||||
 | 
			
		||||
@@ -145,16 +146,15 @@ class token:
 | 
			
		||||
		"""
 | 
			
		||||
		Get longitude
 | 
			
		||||
 | 
			
		||||
		:return: longitude
 | 
			
		||||
		return -- longitude
 | 
			
		||||
		"""
 | 
			
		||||
		return self.location[1]
 | 
			
		||||
 | 
			
		||||
	def startSpectating(self, host):
 | 
			
		||||
		"""
 | 
			
		||||
		Set the spectating user to userID, join spectator stream and chat channel
 | 
			
		||||
		and send required packets to host
 | 
			
		||||
		Set the spectating user to userID
 | 
			
		||||
 | 
			
		||||
		:param host: host osuToken object
 | 
			
		||||
		user -- user object
 | 
			
		||||
		"""
 | 
			
		||||
		# Stop spectating old client
 | 
			
		||||
		self.stopSpectating()
 | 
			
		||||
@@ -194,12 +194,6 @@ class token:
 | 
			
		||||
		log.info("{} is spectating {}".format(self.username, host.username))
 | 
			
		||||
 | 
			
		||||
	def stopSpectating(self):
 | 
			
		||||
		"""
 | 
			
		||||
		Stop spectating, leave spectator stream and channel
 | 
			
		||||
		and send required packets to host
 | 
			
		||||
 | 
			
		||||
		:return:
 | 
			
		||||
		"""
 | 
			
		||||
		# Remove our userID from host's spectators
 | 
			
		||||
		if self.spectating is None:
 | 
			
		||||
			return
 | 
			
		||||
@@ -238,20 +232,36 @@ class token:
 | 
			
		||||
		self.spectating = None
 | 
			
		||||
		self.spectatingUserID = 0
 | 
			
		||||
 | 
			
		||||
	def updatePingTime(self):
 | 
			
		||||
	def setCountry(self, countryID):
 | 
			
		||||
		"""
 | 
			
		||||
		Update latest ping time to current time
 | 
			
		||||
		Set country to countryID
 | 
			
		||||
 | 
			
		||||
		:return:
 | 
			
		||||
		countryID -- numeric country ID. See countryHelper.py
 | 
			
		||||
		"""
 | 
			
		||||
		self.country = countryID
 | 
			
		||||
 | 
			
		||||
	def getCountry(self):
 | 
			
		||||
		"""
 | 
			
		||||
		Get numeric country ID
 | 
			
		||||
 | 
			
		||||
		return -- numeric country ID. See countryHelper.py
 | 
			
		||||
		"""
 | 
			
		||||
		return self.country
 | 
			
		||||
 | 
			
		||||
	def updatePingTime(self):
 | 
			
		||||
		"""Update latest ping time"""
 | 
			
		||||
		self.pingTime = int(time.time())
 | 
			
		||||
 | 
			
		||||
	def setAwayMessage(self, __awayMessage):
 | 
			
		||||
		"""Set a new away message"""
 | 
			
		||||
		self.awayMessage = __awayMessage
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	def joinMatch(self, matchID):
 | 
			
		||||
		"""
 | 
			
		||||
		Set match to matchID, join match stream and channel
 | 
			
		||||
 | 
			
		||||
		:param matchID: new match ID
 | 
			
		||||
		:return:
 | 
			
		||||
		matchID -- new match ID
 | 
			
		||||
		"""
 | 
			
		||||
		# Make sure the match exists
 | 
			
		||||
		if matchID not in glob.matches.matches:
 | 
			
		||||
@@ -311,10 +321,7 @@ class token:
 | 
			
		||||
		"""
 | 
			
		||||
		Kick this user from the server
 | 
			
		||||
		
 | 
			
		||||
		:param message: Notification message to send to this user.
 | 
			
		||||
						Default: "You have been kicked from the server. Please login again."
 | 
			
		||||
		:param reason: Kick reason, used in logs. Default: "kick"
 | 
			
		||||
		:return:
 | 
			
		||||
		message -- Notification message to send to this user. Optional.
 | 
			
		||||
		"""
 | 
			
		||||
		# Send packet to target
 | 
			
		||||
		log.info("{} has been disconnected. ({})".format(self.username, reason))
 | 
			
		||||
@@ -329,10 +336,9 @@ class token:
 | 
			
		||||
		"""
 | 
			
		||||
		Silences this user (db, packet and token)
 | 
			
		||||
 | 
			
		||||
		:param seconds: silence length in seconds
 | 
			
		||||
		:param reason: silence reason
 | 
			
		||||
		:param author: userID of who has silenced the user. Default: 999 (FokaBot)
 | 
			
		||||
		:return:
 | 
			
		||||
		seconds -- silence length in seconds
 | 
			
		||||
		reason -- silence reason
 | 
			
		||||
		author -- userID of who has silenced the target. Optional. Default: 999 (fokabot)
 | 
			
		||||
		"""
 | 
			
		||||
		# Silence in db and token
 | 
			
		||||
		self.silenceEndTime = int(time.time())+seconds
 | 
			
		||||
@@ -348,8 +354,7 @@ class token:
 | 
			
		||||
		"""
 | 
			
		||||
		Silences the user if is spamming.
 | 
			
		||||
 | 
			
		||||
		:param increaseSpamRate: set to True if the user has sent a new message. Default: True
 | 
			
		||||
		:return:
 | 
			
		||||
		increaseSpamRate -- pass True if the user has sent a new message. Optional. Default: True
 | 
			
		||||
		"""
 | 
			
		||||
		# Increase the spam rate if needed
 | 
			
		||||
		if increaseSpamRate:
 | 
			
		||||
@@ -363,7 +368,7 @@ class token:
 | 
			
		||||
		"""
 | 
			
		||||
		Returns True if this user is silenced, otherwise False
 | 
			
		||||
 | 
			
		||||
		:return: True if this user is silenced, otherwise False
 | 
			
		||||
		return -- True/False
 | 
			
		||||
		"""
 | 
			
		||||
		return self.silenceEndTime-int(time.time()) > 0
 | 
			
		||||
 | 
			
		||||
@@ -372,16 +377,12 @@ class token:
 | 
			
		||||
		Returns the seconds left for this user's silence
 | 
			
		||||
		(0 if user is not silenced)
 | 
			
		||||
 | 
			
		||||
		:return: silence seconds left (or 0)
 | 
			
		||||
		return -- silence seconds left
 | 
			
		||||
		"""
 | 
			
		||||
		return max(0, self.silenceEndTime-int(time.time()))
 | 
			
		||||
 | 
			
		||||
	def updateCachedStats(self):
 | 
			
		||||
		"""
 | 
			
		||||
		Update all cached stats for this token
 | 
			
		||||
 | 
			
		||||
		:return:
 | 
			
		||||
		"""
 | 
			
		||||
		"""Update all cached stats for this token"""
 | 
			
		||||
		stats = userUtils.getUserStats(self.userID, self.gameMode)
 | 
			
		||||
		log.debug(str(stats))
 | 
			
		||||
		if stats is None:
 | 
			
		||||
@@ -398,9 +399,8 @@ class token:
 | 
			
		||||
		"""
 | 
			
		||||
		Check if this token is restricted. If so, send fokabot message
 | 
			
		||||
 | 
			
		||||
		:param force:	If True, get restricted value from db.
 | 
			
		||||
						If False, get the cached one. Default: False
 | 
			
		||||
		:return:
 | 
			
		||||
		force --	If True, get restricted value from db.
 | 
			
		||||
					If false, get the cached one. Optional. Default: False
 | 
			
		||||
		"""
 | 
			
		||||
		if force:
 | 
			
		||||
			self.restricted = userUtils.isRestricted(self.userID)
 | 
			
		||||
@@ -411,40 +411,21 @@ class token:
 | 
			
		||||
		"""
 | 
			
		||||
		Set this token as restricted, send FokaBot message to user
 | 
			
		||||
		and send offline packet to everyone
 | 
			
		||||
 | 
			
		||||
		:return:
 | 
			
		||||
		"""
 | 
			
		||||
		self.restricted = True
 | 
			
		||||
		chat.sendMessage("FokaBot",self.username, "Your account is currently in restricted mode. Please visit ripple's website for more information.")
 | 
			
		||||
 | 
			
		||||
	def joinStream(self, name):
 | 
			
		||||
		"""
 | 
			
		||||
		Join a packet stream, or create it if the stream doesn't exist.
 | 
			
		||||
 | 
			
		||||
		:param name: stream name
 | 
			
		||||
		:return:
 | 
			
		||||
		"""
 | 
			
		||||
		glob.streams.join(name, token=self.token)
 | 
			
		||||
		if name not in self.streams:
 | 
			
		||||
			self.streams.append(name)
 | 
			
		||||
 | 
			
		||||
	def leaveStream(self, name):
 | 
			
		||||
		"""
 | 
			
		||||
		Leave a packets stream
 | 
			
		||||
 | 
			
		||||
		:param name: stream name
 | 
			
		||||
		:return:
 | 
			
		||||
		"""
 | 
			
		||||
		glob.streams.leave(name, token=self.token)
 | 
			
		||||
		if name in self.streams:
 | 
			
		||||
			self.streams.remove(name)
 | 
			
		||||
 | 
			
		||||
	def leaveAllStreams(self):
 | 
			
		||||
		"""
 | 
			
		||||
		Leave all joined packet streams
 | 
			
		||||
 | 
			
		||||
		:return:
 | 
			
		||||
		"""
 | 
			
		||||
		for i in self.streams:
 | 
			
		||||
			self.leaveStream(i)
 | 
			
		||||
 | 
			
		||||
@@ -459,4 +440,11 @@ class token:
 | 
			
		||||
		if self.awayMessage == "" or userID in self.sentAway:
 | 
			
		||||
			return False
 | 
			
		||||
		self.sentAway.append(userID)
 | 
			
		||||
		return True
 | 
			
		||||
		return True
 | 
			
		||||
 | 
			
		||||
	def updatePrivileges(self):
 | 
			
		||||
		"""
 | 
			
		||||
		Force updating self.privileges from db
 | 
			
		||||
		:return:
 | 
			
		||||
		"""
 | 
			
		||||
		self.privileges = userUtils.getPrivileges(self.userID)
 | 
			
		||||
@@ -8,19 +8,27 @@ from events import logoutEvent
 | 
			
		||||
from objects import glob
 | 
			
		||||
from objects import osuToken
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class tokenList:
 | 
			
		||||
	"""
 | 
			
		||||
	List of connected osu tokens
 | 
			
		||||
 | 
			
		||||
	tokens -- dictionary. key: token string, value: token object
 | 
			
		||||
	"""
 | 
			
		||||
 | 
			
		||||
	def __init__(self):
 | 
			
		||||
		"""
 | 
			
		||||
		Initialize a tokens list
 | 
			
		||||
		"""
 | 
			
		||||
		self.tokens = {}
 | 
			
		||||
 | 
			
		||||
	def addToken(self, userID, ip = "", irc = False, timeOffset=0, tournament=False):
 | 
			
		||||
		"""
 | 
			
		||||
		Add a token object to tokens list
 | 
			
		||||
 | 
			
		||||
		:param userID: user id associated to that token
 | 
			
		||||
		:param irc: if True, set this token as IRC client
 | 
			
		||||
		:param timeOffset: the time offset from UTC for this user. Default: 0.
 | 
			
		||||
		:param tournament: if True, flag this client as a tournement client. Default: True.
 | 
			
		||||
		:return: token object
 | 
			
		||||
		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, irc=irc, timeOffset=timeOffset, tournament=tournament)
 | 
			
		||||
		self.tokens[newToken.token] = newToken
 | 
			
		||||
@@ -30,8 +38,7 @@ class tokenList:
 | 
			
		||||
		"""
 | 
			
		||||
		Delete a token from token list if it exists
 | 
			
		||||
 | 
			
		||||
		:param token: token string
 | 
			
		||||
		:return:
 | 
			
		||||
		token -- token string
 | 
			
		||||
		"""
 | 
			
		||||
		if token in self.tokens:
 | 
			
		||||
			# Delete session from DB
 | 
			
		||||
@@ -45,8 +52,8 @@ class tokenList:
 | 
			
		||||
		"""
 | 
			
		||||
		Get user ID from a token
 | 
			
		||||
 | 
			
		||||
		:param token: token to find
 | 
			
		||||
		:return: False if not found, userID if found
 | 
			
		||||
		token -- token to find
 | 
			
		||||
		return -- false if not found, userID if found
 | 
			
		||||
		"""
 | 
			
		||||
		# Make sure the token exists
 | 
			
		||||
		if token not in self.tokens:
 | 
			
		||||
@@ -59,9 +66,8 @@ class tokenList:
 | 
			
		||||
		"""
 | 
			
		||||
		Get token from a user ID
 | 
			
		||||
 | 
			
		||||
		:param userID: user ID to find
 | 
			
		||||
		:param ignoreIRC: if True, consider bancho clients only and skip IRC clients
 | 
			
		||||
		:return: False if not found, token object if found
 | 
			
		||||
		userID -- user ID to find
 | 
			
		||||
		return -- False if not found, token object if found
 | 
			
		||||
		"""
 | 
			
		||||
		# Make sure the token exists
 | 
			
		||||
		for _, value in self.tokens.items():
 | 
			
		||||
@@ -73,22 +79,19 @@ class tokenList:
 | 
			
		||||
		# Return none if not found
 | 
			
		||||
		return None
 | 
			
		||||
 | 
			
		||||
	def getTokenFromUsername(self, username, ignoreIRC=False, safe=False):
 | 
			
		||||
	def getTokenFromUsername(self, username, ignoreIRC=False):
 | 
			
		||||
		"""
 | 
			
		||||
		Get an osuToken object from an username
 | 
			
		||||
		Get token from a username
 | 
			
		||||
 | 
			
		||||
		:param username: normal username or safe username
 | 
			
		||||
		:param ignoreIRC: if True, consider bancho clients only and skip IRC clients
 | 
			
		||||
		:param safe: 	if True, username is a safe username,
 | 
			
		||||
						compare it with token's safe username rather than normal username
 | 
			
		||||
		:return: osuToken object or None
 | 
			
		||||
		username -- username to find
 | 
			
		||||
		return -- False if not found, token object if found
 | 
			
		||||
		"""
 | 
			
		||||
		# lowercase
 | 
			
		||||
		who = username.lower() if not safe else username
 | 
			
		||||
		who  = username.lower()
 | 
			
		||||
 | 
			
		||||
		# Make sure the token exists
 | 
			
		||||
		for _, value in self.tokens.items():
 | 
			
		||||
			if (not safe and value.username.lower() == who) or (safe and value.safeUsername == who):
 | 
			
		||||
			if value.username.lower() == who:
 | 
			
		||||
				if ignoreIRC and value.irc:
 | 
			
		||||
					continue
 | 
			
		||||
				return value
 | 
			
		||||
@@ -100,9 +103,9 @@ class tokenList:
 | 
			
		||||
		"""
 | 
			
		||||
		Delete old userID's tokens if found
 | 
			
		||||
 | 
			
		||||
		:param userID: tokens associated to this user will be deleted
 | 
			
		||||
		:return:
 | 
			
		||||
		userID -- tokens associated to this user will be deleted
 | 
			
		||||
		"""
 | 
			
		||||
 | 
			
		||||
		# Delete older tokens
 | 
			
		||||
		for key, value in list(self.tokens.items()):
 | 
			
		||||
			if value.userID == userID:
 | 
			
		||||
@@ -113,10 +116,9 @@ class tokenList:
 | 
			
		||||
		"""
 | 
			
		||||
		Enqueue a packet to multiple users
 | 
			
		||||
 | 
			
		||||
		:param packet: packet bytes to enqueue
 | 
			
		||||
		:param who: userIDs array
 | 
			
		||||
		:param but: if True, enqueue to everyone but users in `who` array
 | 
			
		||||
		:return:
 | 
			
		||||
		packet -- packet bytes to enqueue
 | 
			
		||||
		who -- userIDs array
 | 
			
		||||
		but -- if True, enqueue to everyone but users in who array
 | 
			
		||||
		"""
 | 
			
		||||
		for _, value in self.tokens.items():
 | 
			
		||||
			shouldEnqueue = False
 | 
			
		||||
@@ -132,21 +134,19 @@ class tokenList:
 | 
			
		||||
		"""
 | 
			
		||||
		Enqueue packet(s) to every connected user
 | 
			
		||||
 | 
			
		||||
		:param packet: packet bytes to enqueue
 | 
			
		||||
		:return:
 | 
			
		||||
		packet -- packet bytes to enqueue
 | 
			
		||||
		"""
 | 
			
		||||
		for _, value in self.tokens.items():
 | 
			
		||||
			value.enqueue(packet)
 | 
			
		||||
 | 
			
		||||
	def usersTimeoutCheckLoop(self, timeoutTime = 100, checkTime = 100):
 | 
			
		||||
		"""
 | 
			
		||||
		Start timed out users disconnect loop.
 | 
			
		||||
		This function will be called every `checkTime` seconds and so on, forever.
 | 
			
		||||
		Deletes all timed out users.
 | 
			
		||||
		If called once, will recall after checkTime seconds and so on, forever
 | 
			
		||||
		CALL THIS FUNCTION ONLY ONCE!
 | 
			
		||||
 | 
			
		||||
		:param timeoutTime: seconds of inactivity required to disconnect someone. Default: 100
 | 
			
		||||
		:param checkTime: seconds between loops. Default: 100
 | 
			
		||||
		:return:
 | 
			
		||||
		timeoutTime - seconds of inactivity required to disconnect someone (Default: 100)
 | 
			
		||||
		checkTime - seconds between loops (Default: 100)
 | 
			
		||||
		"""
 | 
			
		||||
		log.debug("Checking timed out clients")
 | 
			
		||||
		timedOutTokens = []		# timed out users
 | 
			
		||||
@@ -170,11 +170,8 @@ class tokenList:
 | 
			
		||||
 | 
			
		||||
	def spamProtectionResetLoop(self):
 | 
			
		||||
		"""
 | 
			
		||||
		Start spam protection reset loop.
 | 
			
		||||
		Called every 10 seconds.
 | 
			
		||||
		Reset spam rate every 10 seconds.
 | 
			
		||||
		CALL THIS FUNCTION ONLY ONCE!
 | 
			
		||||
 | 
			
		||||
		:return:
 | 
			
		||||
		"""
 | 
			
		||||
		# Reset spamRate for every token
 | 
			
		||||
		for _, value in self.tokens.items():
 | 
			
		||||
@@ -187,22 +184,20 @@ class tokenList:
 | 
			
		||||
		"""
 | 
			
		||||
		Truncate bancho_sessions table.
 | 
			
		||||
		Call at bancho startup to delete old cached sessions
 | 
			
		||||
 | 
			
		||||
		:return:
 | 
			
		||||
		"""
 | 
			
		||||
		glob.db.execute("TRUNCATE TABLE bancho_sessions")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	def tokenExists(self, username = "", userID = -1):
 | 
			
		||||
		"""
 | 
			
		||||
		Check if a token exists
 | 
			
		||||
		Use username or userid, not both at the same time.
 | 
			
		||||
		Check if a token exists (aka check if someone is connected)
 | 
			
		||||
 | 
			
		||||
		:param username: Optional.
 | 
			
		||||
		:param userID: Optional.
 | 
			
		||||
		:return: True if it exists, otherwise False
 | 
			
		||||
		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
 | 
			
		||||
			return True if self.getTokenFromUsername(username) is not None else False
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										33
									
								
								pep.py
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								pep.py
									
									
									
									
									
								
							@@ -9,7 +9,6 @@ import tornado.httpserver
 | 
			
		||||
import tornado.ioloop
 | 
			
		||||
import tornado.web
 | 
			
		||||
from raven.contrib.tornado import AsyncSentryClient
 | 
			
		||||
import redis
 | 
			
		||||
 | 
			
		||||
from common import generalUtils
 | 
			
		||||
from common.constants import bcolors
 | 
			
		||||
@@ -83,7 +82,7 @@ if __name__ == "__main__":
 | 
			
		||||
 | 
			
		||||
		# Connect to db
 | 
			
		||||
		try:
 | 
			
		||||
			consoleHelper.printNoNl("> Connecting to MySQL database... ")
 | 
			
		||||
			consoleHelper.printNoNl("> Connecting to MySQL database...")
 | 
			
		||||
			glob.db = dbConnector.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()
 | 
			
		||||
@@ -93,29 +92,6 @@ if __name__ == "__main__":
 | 
			
		||||
			consoleHelper.printColored("[!] Error while connection to database. Please check your config.ini and run the server again", bcolors.RED)
 | 
			
		||||
			raise
 | 
			
		||||
 | 
			
		||||
		# Connect to redis
 | 
			
		||||
		try:
 | 
			
		||||
			consoleHelper.printNoNl("> Connecting to redis... ")
 | 
			
		||||
			glob.redis = redis.Redis(glob.conf.config["redis"]["host"], glob.conf.config["redis"]["port"], glob.conf.config["redis"]["database"], glob.conf.config["redis"]["password"])
 | 
			
		||||
			glob.redis.ping()
 | 
			
		||||
			consoleHelper.printNoNl(" ")
 | 
			
		||||
			consoleHelper.printDone()
 | 
			
		||||
		except:
 | 
			
		||||
			# Exception while connecting to db
 | 
			
		||||
			consoleHelper.printError()
 | 
			
		||||
			consoleHelper.printColored("[!] Error while connection to redis. Please check your config.ini and run the server again", bcolors.RED)
 | 
			
		||||
			raise
 | 
			
		||||
 | 
			
		||||
		# Empty redis cache
 | 
			
		||||
		try:
 | 
			
		||||
			glob.redis.eval("return redis.call('del', unpack(redis.call('keys', ARGV[1])))", 0, "peppy:*")
 | 
			
		||||
		except redis.exceptions.ResponseError:
 | 
			
		||||
			# Script returns error if there are no keys starting with peppy:*
 | 
			
		||||
			pass
 | 
			
		||||
 | 
			
		||||
		# Save peppy version in redis
 | 
			
		||||
		glob.redis.set("peppy:version", glob.VERSION)
 | 
			
		||||
 | 
			
		||||
		# Load bancho_settings
 | 
			
		||||
		try:
 | 
			
		||||
			consoleHelper.printNoNl("> Loading bancho settings from DB... ")
 | 
			
		||||
@@ -175,6 +151,11 @@ if __name__ == "__main__":
 | 
			
		||||
		glob.tokens.spamProtectionResetLoop()
 | 
			
		||||
		consoleHelper.printDone()
 | 
			
		||||
 | 
			
		||||
		# Cache user ids
 | 
			
		||||
		consoleHelper.printNoNl("> Caching user IDs... ")
 | 
			
		||||
		userUtils.cacheUserIDs()
 | 
			
		||||
		consoleHelper.printDone()
 | 
			
		||||
 | 
			
		||||
		# Localize warning
 | 
			
		||||
		glob.localize = generalUtils.stringToBool(glob.conf.config["localize"]["enable"])
 | 
			
		||||
		if not glob.localize:
 | 
			
		||||
@@ -229,7 +210,7 @@ if __name__ == "__main__":
 | 
			
		||||
						datadogClient.periodicCheck("ram_file_locks", lambda: generalUtils.getTotalSize(glob.fLocks)),
 | 
			
		||||
						datadogClient.periodicCheck("ram_datadog", lambda: generalUtils.getTotalSize(glob.datadogClient)),
 | 
			
		||||
						datadogClient.periodicCheck("ram_verified_cache", lambda: generalUtils.getTotalSize(glob.verifiedCache)),
 | 
			
		||||
						#datadogClient.periodicCheck("ram_userid_cache", lambda: generalUtils.getTotalSize(glob.userIDCache)),
 | 
			
		||||
						datadogClient.periodicCheck("ram_userid_cache", lambda: generalUtils.getTotalSize(glob.userIDCache)),
 | 
			
		||||
						#datadogClient.periodicCheck("ram_pool", lambda: generalUtils.getTotalSize(glob.pool)),
 | 
			
		||||
						datadogClient.periodicCheck("ram_irc", lambda: generalUtils.getTotalSize(glob.ircServer)),
 | 
			
		||||
						datadogClient.periodicCheck("ram_tornado", lambda: generalUtils.getTotalSize(glob.application)),
 | 
			
		||||
 
 | 
			
		||||
@@ -4,5 +4,4 @@ mysqlclient
 | 
			
		||||
psutil
 | 
			
		||||
raven
 | 
			
		||||
bcrypt>=3.1.1
 | 
			
		||||
dill
 | 
			
		||||
redis
 | 
			
		||||
dill
 | 
			
		||||
		Reference in New Issue
	
	Block a user