342 lines
8.6 KiB
Python
342 lines
8.6 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
|
|
|
|
# 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.rankedScore = 0
|
|
self.accuracy = 0.0
|
|
self.playcount = 0
|
|
self.totalScore = 0
|
|
self.gameRank = 0
|
|
self.pp = 0
|
|
|
|
# Generate/set token
|
|
if token != 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)
|
|
|
|
def enqueue(self, bytes):
|
|
"""
|
|
Add bytes (packets) to queue
|
|
|
|
bytes -- (packet) bytes to enqueue
|
|
"""
|
|
if self.irc == False:
|
|
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
|
|
targetToken = glob.tokens.getTokenFromUserID(target)
|
|
if targetToken != 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.tokens.enqueueAll(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 == True:
|
|
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 == 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 == True:
|
|
self.restricted = userHelper.isRestricted(self.userID)
|
|
if self.restricted == True:
|
|
self.setRestricted()
|
|
|
|
def setRestricted(self):
|
|
"""
|
|
Set this token as restricted, send FokaBot message to user
|
|
and send offline packet to everyone
|
|
"""
|
|
self.restricted = True
|
|
chat.sendMessage("FokaBot",self.username, "Your account is currently in restricted mode. Please visit ripple's website for more information.")
|