363 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			363 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from constants import actions
 | |
| from constants import gameModes
 | |
| from helpers import userHelper
 | |
| from constants import serverPackets
 | |
| from events import logoutEvent
 | |
| from helpers import logHelper as log
 | |
| from objects import glob
 | |
| import uuid
 | |
| import time
 | |
| import threading
 | |
| from helpers import chatHelper as chat
 | |
| 
 | |
| class token:
 | |
| 
 | |
| 	def __init__(self, userID, token_ = None, ip ="", irc = False, timeOffset = 0):
 | |
| 		"""
 | |
| 		Create a token object and set userID and token
 | |
| 
 | |
| 		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 = userHelper.getUsername(self.userID)
 | |
| 		self.privileges = userHelper.getPrivileges(self.userID)
 | |
| 		self.admin = userHelper.isInPrivilegeGroup(self.userID, "developer") or userHelper.isInPrivilegeGroup(self.userID, "community manager")
 | |
| 		self.irc = irc
 | |
| 		self.restricted = userHelper.isRestricted(self.userID)
 | |
| 		self.loginTime = int(time.time())
 | |
| 		self.pingTime = self.loginTime
 | |
| 		self.timeOffset = timeOffset
 | |
| 		self.lock = threading.Lock()	# Sync primitive
 | |
| 		self.streams = []
 | |
| 
 | |
| 		# Default variables
 | |
| 		self.spectators = []
 | |
| 		self.spectating = 0
 | |
| 		self.location = [0,0]
 | |
| 		self.joinedChannels = []
 | |
| 		self.ip = ip
 | |
| 		self.country = 0
 | |
| 		self.location = [0,0]
 | |
| 		self.awayMessage = ""
 | |
| 		self.matchID = -1
 | |
| 		self.tillerino = [0,0,-1.0]	# beatmap, mods, acc
 | |
| 		self.silenceEndTime = 0
 | |
| 		self.queue = bytes()
 | |
| 
 | |
| 		# Spam protection
 | |
| 		self.spamRate = 0
 | |
| 
 | |
| 		# Stats cache
 | |
| 		self.actionID = actions.IDLE
 | |
| 		self.actionText = ""
 | |
| 		self.actionMd5 = ""
 | |
| 		self.actionMods = 0
 | |
| 		self.gameMode = gameModes.std
 | |
| 		self.beatmapID = 0
 | |
| 		self.rankedScore = 0
 | |
| 		self.accuracy = 0.0
 | |
| 		self.playcount = 0
 | |
| 		self.totalScore = 0
 | |
| 		self.gameRank = 0
 | |
| 		self.pp = 0
 | |
| 
 | |
| 		# Generate/set token
 | |
| 		if token_ is not None:
 | |
| 			self.token = token_
 | |
| 		else:
 | |
| 			self.token = str(uuid.uuid4())
 | |
| 
 | |
| 		# Set stats
 | |
| 		self.updateCachedStats()
 | |
| 
 | |
| 		# If we have a valid ip, save bancho session in DB so we can cache LETS logins
 | |
| 		if ip != "":
 | |
| 			userHelper.saveBanchoSession(self.userID, self.ip)
 | |
| 
 | |
| 		# Join main stream
 | |
| 		self.joinStream("main")
 | |
| 
 | |
| 	def enqueue(self, bytes_):
 | |
| 		"""
 | |
| 		Add bytes (packets) to queue
 | |
| 
 | |
| 		bytes -- (packet) bytes to enqueue
 | |
| 		"""
 | |
| 		if not self.irc:
 | |
| 			self.queue += bytes_
 | |
| 
 | |
| 
 | |
| 	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
 | |
| 
 | |
| 		channel -- channel name
 | |
| 		"""
 | |
| 		if channel not in self.joinedChannels:
 | |
| 			self.joinedChannels.append(channel)
 | |
| 
 | |
| 	def partChannel(self, channel):
 | |
| 		"""
 | |
| 		Remove channel from joined channels list
 | |
| 
 | |
| 		channel -- channel name
 | |
| 		"""
 | |
| 		if channel in self.joinedChannels:
 | |
| 			self.joinedChannels.remove(channel)
 | |
| 
 | |
| 	def setLocation(self, location):
 | |
| 		"""
 | |
| 		Set location (latitude and longitude)
 | |
| 
 | |
| 		location -- [latitude, longitude]
 | |
| 		"""
 | |
| 		self.location = location
 | |
| 
 | |
| 	def getLatitude(self):
 | |
| 		"""
 | |
| 		Get latitude
 | |
| 
 | |
| 		return -- latitude
 | |
| 		"""
 | |
| 		return self.location[0]
 | |
| 
 | |
| 	def getLongitude(self):
 | |
| 		"""
 | |
| 		Get longitude
 | |
| 
 | |
| 		return -- longitude
 | |
| 		"""
 | |
| 		return self.location[1]
 | |
| 
 | |
| 	def startSpectating(self, userID):
 | |
| 		"""
 | |
| 		Set the spectating user to userID
 | |
| 
 | |
| 		userID -- target userID
 | |
| 		"""
 | |
| 		self.spectating = userID
 | |
| 
 | |
| 	def stopSpectating(self):
 | |
| 		# Remove our userID from host's spectators
 | |
| 		target = self.spectating
 | |
| 		if self.spectating == 0:
 | |
| 			return
 | |
| 		targetToken = glob.tokens.getTokenFromUserID(target)
 | |
| 		if targetToken is not None:
 | |
| 			# Remove us from host's spectators list
 | |
| 			targetToken.removeSpectator(self.userID)
 | |
| 
 | |
| 			# Send the spectator left packet to host
 | |
| 			targetToken.enqueue(serverPackets.removeSpectator(self.userID))
 | |
| 			for c in targetToken.spectators:
 | |
| 				spec = glob.tokens.getTokenFromUserID(c)
 | |
| 				spec.enqueue(serverPackets.fellowSpectatorLeft(self.userID))
 | |
| 
 | |
| 			# If nobody is spectating the host anymore, close #spectator channel
 | |
| 			if len(targetToken.spectators) == 0:
 | |
| 				chat.partChannel(token=targetToken, channel="#spect_{}".format(target), kick=True)
 | |
| 
 | |
| 		# Part #spectator channel
 | |
| 		chat.partChannel(token=self, channel="#spect_{}".format(target), kick=True)
 | |
| 
 | |
| 		# Set our spectating user to 0
 | |
| 		self.spectating = 0
 | |
| 
 | |
| 		# Console output
 | |
| 		log.info("{} are no longer spectating {}".format(self.username, target))
 | |
| 
 | |
| 	def partMatch(self):
 | |
| 		# Make sure we are in a match
 | |
| 		if self.matchID == -1:
 | |
| 			return
 | |
| 
 | |
| 		# Part #multiplayer channel
 | |
| 		chat.partChannel(token=self, channel="#multi_{}".format(self.matchID), kick=True)
 | |
| 
 | |
| 		# Make sure the match exists
 | |
| 		if self.matchID not in glob.matches.matches:
 | |
| 			return
 | |
| 
 | |
| 		# The match exists, get object
 | |
| 		match = glob.matches.matches[self.matchID]
 | |
| 
 | |
| 		# Set slot to free
 | |
| 		match.userLeft(self.userID)
 | |
| 
 | |
| 		# Set usertoken match to -1
 | |
| 		self.matchID = -1
 | |
| 
 | |
| 	def addSpectator(self, userID):
 | |
| 		"""
 | |
| 		Add userID to our spectators
 | |
| 
 | |
| 		userID -- new spectator userID
 | |
| 		"""
 | |
| 		# Add userID to spectators if not already in
 | |
| 		if userID not in self.spectators:
 | |
| 			self.spectators.append(userID)
 | |
| 
 | |
| 	def removeSpectator(self, userID):
 | |
| 		"""
 | |
| 		Remove userID from our spectators
 | |
| 
 | |
| 		userID -- old spectator userID
 | |
| 		"""
 | |
| 		# Remove spectator
 | |
| 		if userID in self.spectators:
 | |
| 			self.spectators.remove(userID)
 | |
| 
 | |
| 	def setCountry(self, countryID):
 | |
| 		"""
 | |
| 		Set country to countryID
 | |
| 
 | |
| 		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
 | |
| 
 | |
| 		matchID -- new match ID
 | |
| 		"""
 | |
| 		self.matchID = matchID
 | |
| 
 | |
| 	def kick(self, message="You have been kicked from the server. Please login again."):
 | |
| 		"""
 | |
| 		Kick this user from the server
 | |
| 		
 | |
| 		message -- Notification message to send to this user. Optional.
 | |
| 		"""
 | |
| 		# Send packet to target
 | |
| 		log.info("{} has been disconnected. (kick)".format(self.username))
 | |
| 		if message != "":
 | |
| 			self.enqueue(serverPackets.notification(message))
 | |
| 		self.enqueue(serverPackets.loginFailed())
 | |
| 
 | |
| 		# Logout event
 | |
| 		logoutEvent.handle(self, None)
 | |
| 
 | |
| 	def silence(self, seconds, reason, author = 999):
 | |
| 		"""
 | |
| 		Silences this user (db, packet and token)
 | |
| 
 | |
| 		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
 | |
| 		userHelper.silence(self.userID, seconds, reason, author)
 | |
| 
 | |
| 		# Send silence packet to target
 | |
| 		self.enqueue(serverPackets.silenceEndTime(seconds))
 | |
| 
 | |
| 		# Send silenced packet to everyone else
 | |
| 		glob.streams.broadcast("main", serverPackets.userSilenced(self.userID))
 | |
| 
 | |
| 	def spamProtection(self, increaseSpamRate = True):
 | |
| 		"""
 | |
| 		Silences the user if is spamming.
 | |
| 
 | |
| 		increaseSpamRate -- pass True if the user has sent a new message. Optional. Default: True
 | |
| 		"""
 | |
| 		# Increase the spam rate if needed
 | |
| 		if increaseSpamRate:
 | |
| 			self.spamRate += 1
 | |
| 
 | |
| 		# Silence the user if needed
 | |
| 		if self.spamRate > 10:
 | |
| 			self.silence(1800, "Spamming (auto spam protection)")
 | |
| 
 | |
| 	def isSilenced(self):
 | |
| 		"""
 | |
| 		Returns True if this user is silenced, otherwise False
 | |
| 
 | |
| 		return -- True/False
 | |
| 		"""
 | |
| 		return self.silenceEndTime-int(time.time()) > 0
 | |
| 
 | |
| 	def getSilenceSecondsLeft(self):
 | |
| 		"""
 | |
| 		Returns the seconds left for this user's silence
 | |
| 		(0 if user is not silenced)
 | |
| 
 | |
| 		return -- silence seconds left
 | |
| 		"""
 | |
| 		return max(0, self.silenceEndTime-int(time.time()))
 | |
| 
 | |
| 	def updateCachedStats(self):
 | |
| 		"""Update all cached stats for this token"""
 | |
| 		stats = userHelper.getUserStats(self.userID, self.gameMode)
 | |
| 		log.debug(str(stats))
 | |
| 		if stats is None:
 | |
| 			log.warning("Stats query returned None")
 | |
| 			return
 | |
| 		self.rankedScore = stats["rankedScore"]
 | |
| 		self.accuracy = stats["accuracy"]/100
 | |
| 		self.playcount = stats["playcount"]
 | |
| 		self.totalScore = stats["totalScore"]
 | |
| 		self.gameRank = stats["gameRank"]
 | |
| 		self.pp = stats["pp"]
 | |
| 
 | |
| 	def checkRestricted(self, force=False):
 | |
| 		"""
 | |
| 		Check if this token is restricted. If so, send fokabot message
 | |
| 
 | |
| 		force --	If True, get restricted value from db.
 | |
| 					If false, get the cached one. Optional. Default: False
 | |
| 		"""
 | |
| 		if force:
 | |
| 			self.restricted = userHelper.isRestricted(self.userID)
 | |
| 		if self.restricted:
 | |
| 			self.setRestricted()
 | |
| 
 | |
| 	def setRestricted(self):
 | |
| 		"""
 | |
| 		Set this token as restricted, send FokaBot message to user
 | |
| 		and send offline packet to everyone
 | |
| 		"""
 | |
| 		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):
 | |
| 		glob.streams.join(name, self)
 | |
| 		if name not in self.streams:
 | |
| 			self.streams.append(name)
 | |
| 
 | |
| 	def leaveStream(self, name):
 | |
| 		glob.streams.leave(name, self)
 | |
| 		if name in self.streams:
 | |
| 			self.streams.remove(name)
 | |
| 
 | |
| 	def leaveAllStreams(self):
 | |
| 		for i in self.streams:
 | |
| 			self.leaveStream(i) |