1637 lines
49 KiB
Python
1637 lines
49 KiB
Python
import time
|
|
from _mysql import ProgrammingError
|
|
import requests
|
|
import json
|
|
|
|
from common import generalUtils
|
|
from common.constants import gameModes
|
|
from common.constants import privileges
|
|
from common.log import logUtils as log
|
|
from common.ripple import passwordUtils, scoreUtils
|
|
from objects import glob
|
|
|
|
def logUserLog(log,fileMd5,userID, gameMode, scoreid):
|
|
glob.db.execute("INSERT INTO users_logs (user, log, time, game_mode, scoreid, beatmap_md5) VALUES (%s, %s, %s, %s, %s, %s)",[userID, log, int(time.time()), gameMode, scoreid, fileMd5])
|
|
return True
|
|
def logUserLogX(log,fileMd5,userID, gameMode):
|
|
glob.db.execute("INSERT INTO users_logs (user, log, time, game_mode, beatmap_md5) VALUES (%s, %s, %s, %s, %s)",[userID, log, int(time.time()), gameMode, fileMd5])
|
|
return True
|
|
|
|
def getBeatmapTime(beatmapID):
|
|
p = 0
|
|
r = requests.get("http://cg.mxr.lol/api/b/{}".format(beatmapID)).text
|
|
if r != "null\n":
|
|
p = json.loads(r)['TotalLength']
|
|
|
|
return p
|
|
|
|
def incrementPlaytime(userID, gameMode=0, length=0):
|
|
modeForDB = gameModes.getGameModeForDB(gameMode)
|
|
result = glob.db.fetch("SELECT playtime_{gm} as playtime FROM users_stats WHERE id = %s".format(gm=modeForDB), [userID])
|
|
if result is not None:
|
|
glob.db.execute("UPDATE users_stats SET playtime_{gm} = %s WHERE id = %s".format(gm=modeForDB), [(int(result['playtime'])+int(length)), userID])
|
|
else:
|
|
print("something went wrong...")
|
|
|
|
|
|
def incrementPlaytimeRX(userID, gameMode=0, length=0):
|
|
modeForDB = gameModes.getGameModeForDB(gameMode)
|
|
result = glob.db.fetch("SELECT playtime_{gm}_rx as playtime FROM users_stats WHERE id = %s".format(gm=modeForDB), [userID])
|
|
if result is not None:
|
|
glob.db.execute("UPDATE users_stats SET playtime_{gm}_rx = %s WHERE id = %s".format(gm=modeForDB), [(int(result['playtime'])+int(length)), userID])
|
|
else:
|
|
print("something went wrong...")
|
|
|
|
|
|
def incrementPlaytimeAP(userID, gameMode=0, length=0):
|
|
modeForDB = gameModes.getGameModeForDB(gameMode)
|
|
result = glob.db.fetch("SELECT playtime_{gm}_ap as playtime FROM users_stats WHERE id = %s".format(gm=modeForDB), [userID])
|
|
if result is not None:
|
|
glob.db.execute("UPDATE users_stats SET playtime_{gm}_ap = %s WHERE id = %s".format(gm=modeForDB), [(int(result['playtime'])+int(length)), userID])
|
|
else:
|
|
print("something went wrong...")
|
|
|
|
|
|
def getUserStats(userID, gameMode):
|
|
"""
|
|
Get all user stats relative to `gameMode`
|
|
|
|
:param userID:
|
|
:param gameMode: game mode number
|
|
:return: dictionary with result
|
|
"""
|
|
modeForDB = gameModes.getGameModeForDB(gameMode)
|
|
# Get stats
|
|
stats = glob.db.fetch("""SELECT
|
|
ranked_score_{gm} AS rankedScore,
|
|
avg_accuracy_{gm} AS accuracy,
|
|
playcount_{gm} AS playcount,
|
|
total_score_{gm} AS totalScore,
|
|
pp_{gm} AS pp
|
|
FROM users_stats WHERE id = %s LIMIT 1""".format(gm=modeForDB), [userID])
|
|
|
|
# Get game rank
|
|
stats["gameRank"] = getGameRank(userID, gameMode)
|
|
|
|
# Return stats + game rank
|
|
return stats
|
|
|
|
def getUserStatsRx(userID, gameMode):
|
|
"""
|
|
Get all user stats relative to `gameMode`
|
|
|
|
:param userID:
|
|
:param gameMode: game mode number
|
|
:return: dictionary with result
|
|
"""
|
|
modeForDB = gameModes.getGameModeForDB(gameMode)
|
|
if gameMode == MANIA:
|
|
|
|
# Get stats
|
|
stats = glob.db.fetch("""SELECT
|
|
ranked_score_{gm} AS rankedScore,
|
|
avg_accuracy_{gm} AS accuracy,
|
|
playcount_{gm} AS playcount,
|
|
total_score_{gm} AS totalScore,
|
|
pp_{gm} AS pp
|
|
FROM users_stats WHERE id = %s LIMIT 1""".format(gm=modeForDB), [userID])
|
|
|
|
# Get game rank
|
|
else:
|
|
# Get stats
|
|
stats = glob.db.fetch("""SELECT
|
|
ranked_score_{gm}_rx AS rankedScore,
|
|
avg_accuracy_{gm}_rx AS accuracy,
|
|
playcount_{gm}_rx AS playcount,
|
|
total_score_{gm}_rx AS totalScore,
|
|
pp_{gm}_rx AS pp
|
|
FROM users_stats WHERE id = %s LIMIT 1""".format(gm=modeForDB), [userID])
|
|
|
|
# Get game rank
|
|
stats["gameRank"] = getGameRankRx(userID, gameMode)
|
|
|
|
# Return stats + game rank
|
|
return stats
|
|
|
|
def getUserStatsAp(userID, gameMode):
|
|
"""
|
|
Get all user stats relative to `gameMode`
|
|
|
|
:param userID:
|
|
:param gameMode: game mode number
|
|
:return: dictionary with result
|
|
"""
|
|
modeForDB = gameModes.getGameModeForDB(gameMode)
|
|
if gameMode == MANIA:
|
|
|
|
# Get stats
|
|
stats = glob.db.fetch("""SELECT
|
|
ranked_score_{gm} AS rankedScore,
|
|
avg_accuracy_{gm} AS accuracy,
|
|
playcount_{gm} AS playcount,
|
|
total_score_{gm} AS totalScore,
|
|
pp_{gm} AS pp
|
|
FROM users_stats WHERE id = %s LIMIT 1""".format(gm=modeForDB), [userID])
|
|
|
|
# Get game rank
|
|
else:
|
|
# Get stats
|
|
stats = glob.db.fetch("""SELECT
|
|
ranked_score_{gm}_ap AS rankedScore,
|
|
avg_accuracy_{gm}_ap AS accuracy,
|
|
playcount_{gm}_ap AS playcount,
|
|
total_score_{gm}_ap AS totalScore,
|
|
pp_{gm}_auto AS pp
|
|
FROM users_stats WHERE id = %s LIMIT 1""".format(gm=modeForDB), [userID])
|
|
|
|
# Get game rank
|
|
stats["gameRank"] = getGameRankAp(userID, gameMode)
|
|
|
|
# Return stats + game rank
|
|
return stats
|
|
|
|
def getIDSafe(_safeUsername):
|
|
"""
|
|
Get user ID from a safe username
|
|
:param _safeUsername: safe username
|
|
:return: None if the user doesn't exist, else user id
|
|
"""
|
|
result = glob.db.fetch("SELECT id FROM users WHERE username_safe = %s LIMIT 1", [_safeUsername])
|
|
if result is not None:
|
|
return result["id"]
|
|
return None
|
|
|
|
def getID(username):
|
|
"""
|
|
Get username's user ID from userID redis cache (if cache hit)
|
|
or from db (and cache it for other requests) if cache miss
|
|
|
|
:param username: user
|
|
:return: user id or 0 if user doesn't exist
|
|
"""
|
|
# Get userID from redis
|
|
usernameSafe = safeUsername(username)
|
|
userID = glob.redis.get("ripple:userid_cache:{}".format(usernameSafe))
|
|
|
|
if userID is None:
|
|
# If it's not in redis, get it from mysql
|
|
userID = getIDSafe(usernameSafe)
|
|
|
|
# If it's invalid, return 0
|
|
if userID is None:
|
|
return 0
|
|
|
|
# Otherwise, save it in redis and return it
|
|
glob.redis.set("ripple:userid_cache:{}".format(usernameSafe), userID, 3600) # expires in 1 hour
|
|
return userID
|
|
|
|
# Return userid from redis
|
|
return int(userID)
|
|
|
|
def getUsername(userID):
|
|
"""
|
|
Get userID's username
|
|
|
|
:param userID: user id
|
|
:return: username or None
|
|
"""
|
|
result = glob.db.fetch("SELECT username FROM users WHERE id = %s LIMIT 1", [userID])
|
|
if result is None:
|
|
return None
|
|
return result["username"]
|
|
|
|
def getSafeUsername(userID):
|
|
"""
|
|
Get userID's safe username
|
|
|
|
:param userID: user id
|
|
:return: username or None
|
|
"""
|
|
result = glob.db.fetch("SELECT username_safe FROM users WHERE id = %s LIMIT 1", [userID])
|
|
if result is None:
|
|
return None
|
|
return result["username_safe"]
|
|
|
|
def exists(userID):
|
|
"""
|
|
Check if given userID exists
|
|
|
|
:param userID: user id to check
|
|
:return: True if the user exists, else False
|
|
"""
|
|
return True if glob.db.fetch("SELECT id FROM users WHERE id = %s LIMIT 1", [userID]) is not None else False
|
|
|
|
def checkLogin(userID, password, ip=""):
|
|
"""
|
|
Check userID's login with specified password
|
|
|
|
:param userID: user id
|
|
:param password: md5 password
|
|
:param ip: request IP (used to check active bancho sessions). Optional.
|
|
:return: True if user id and password combination is valid, else False
|
|
"""
|
|
# Check cached bancho session
|
|
banchoSession = False
|
|
if ip != "":
|
|
banchoSession = checkBanchoSession(userID, ip)
|
|
|
|
# Return True if there's a bancho session for this user from that ip
|
|
if banchoSession:
|
|
return True
|
|
|
|
# Otherwise, check password
|
|
# Get password data
|
|
passwordData = glob.db.fetch("SELECT password_md5, salt, password_version FROM users WHERE id = %s LIMIT 1", [userID])
|
|
|
|
# Make sure the query returned something
|
|
if passwordData is None:
|
|
return False
|
|
|
|
# Return valid/invalid based on the password version.
|
|
if passwordData["password_version"] == 2:
|
|
return passwordUtils.checkNewPassword(password, passwordData["password_md5"])
|
|
if passwordData["password_version"] == 1:
|
|
ok = passwordUtils.checkOldPassword(password, passwordData["salt"], passwordData["password_md5"])
|
|
if not ok:
|
|
return False
|
|
newpass = passwordUtils.genBcrypt(password)
|
|
glob.db.execute("UPDATE users SET password_md5=%s, salt='', password_version='2' WHERE id = %s LIMIT 1", [newpass, userID])
|
|
|
|
def getRequiredScoreForLevel(level):
|
|
"""
|
|
Return score required to reach a level
|
|
|
|
:param level: level to reach
|
|
:return: required score
|
|
"""
|
|
if level <= 100:
|
|
if level >= 2:
|
|
return 5000 / 3 * (4 * (level ** 3) - 3 * (level ** 2) - level) + 1.25 * (1.8 ** (level - 60))
|
|
elif level <= 0 or level == 1:
|
|
return 1 # Should be 0, but we get division by 0 below so set to 1
|
|
elif level >= 101:
|
|
return 26931190829 + 100000000000 * (level - 100)
|
|
|
|
def getLevel(totalScore):
|
|
"""
|
|
Return level from totalScore
|
|
|
|
:param totalScore: total score
|
|
:return: level
|
|
"""
|
|
level = 1
|
|
while True:
|
|
# if the level is > 8000, it's probably an endless loop. terminate it.
|
|
if level > 8000:
|
|
return level
|
|
|
|
# Calculate required score
|
|
reqScore = getRequiredScoreForLevel(level)
|
|
|
|
# Check if this is our level
|
|
if totalScore <= reqScore:
|
|
# Our level, return it and break
|
|
return level - 1
|
|
else:
|
|
# Not our level, calculate score for next level
|
|
level += 1
|
|
|
|
def updateLevel(userID, gameMode=0, totalScore=0):
|
|
"""
|
|
Update level in DB for userID relative to gameMode
|
|
|
|
:param userID: user id
|
|
:param gameMode: game mode number
|
|
:param totalScore: new total score
|
|
:return:
|
|
"""
|
|
# Make sure the user exists
|
|
# if not exists(userID):
|
|
# return
|
|
|
|
# Get total score from db if not passed
|
|
mode = scoreUtils.readableGameMode(gameMode)
|
|
if totalScore == 0:
|
|
totalScore = glob.db.fetch(
|
|
"SELECT total_score_{m} as total_score FROM users_stats WHERE id = %s LIMIT 1".format(m=mode), [userID])
|
|
if totalScore:
|
|
totalScore = totalScore["total_score"]
|
|
|
|
# Calculate level from totalScore
|
|
level = getLevel(totalScore)
|
|
|
|
# Save new level
|
|
glob.db.execute("UPDATE users_stats SET level_{m} = %s WHERE id = %s LIMIT 1".format(m=mode), [level, userID])
|
|
|
|
def updateLevelRX(userID, gameMode=0, totalScore=0):
|
|
"""
|
|
Update level in DB for userID relative to gameMode
|
|
|
|
:param userID: user id
|
|
:param gameMode: game mode number
|
|
:param totalScore: new total score
|
|
:return:
|
|
"""
|
|
# Make sure the user exists
|
|
# if not exists(userID):
|
|
# return
|
|
|
|
# Get total score from db if not passed
|
|
mode = scoreUtils.readableGameMode(gameMode)
|
|
if totalScore == 0:
|
|
totalScore = glob.db.fetch(
|
|
"SELECT total_score_{m}_rx as total_score FROM users_stats WHERE id = %s LIMIT 1".format(m=mode), [userID])
|
|
if totalScore:
|
|
totalScore = totalScore["total_score"]
|
|
|
|
# Calculate level from totalScore
|
|
level = getLevel(totalScore)
|
|
|
|
# Save new level
|
|
glob.db.execute("UPDATE users_stats SET level_{m}_rx = %s WHERE id = %s LIMIT 1".format(m=mode), [level, userID])
|
|
|
|
def updateLevelAP(userID, gameMode=0, totalScore=0):
|
|
"""
|
|
Update level in DB for userID relative to gameMode
|
|
|
|
:param userID: user id
|
|
:param gameMode: game mode number
|
|
:param totalScore: new total score
|
|
:return:
|
|
"""
|
|
# Make sure the user exists
|
|
# if not exists(userID):
|
|
# return
|
|
|
|
# Get total score from db if not passed
|
|
mode = scoreUtils.readableGameMode(gameMode)
|
|
if totalScore == 0:
|
|
totalScore = glob.db.fetch(
|
|
"SELECT total_score_{m}_ap as total_score FROM users_stats WHERE id = %s LIMIT 1".format(m=mode), [userID])
|
|
if totalScore:
|
|
totalScore = totalScore["total_score"]
|
|
|
|
# Calculate level from totalScore
|
|
level = getLevel(totalScore)
|
|
|
|
# Save new level
|
|
glob.db.execute("UPDATE users_stats SET level_{m}_ap = %s WHERE id = %s LIMIT 1".format(m=mode), [level, userID])
|
|
|
|
def calculateAccuracy(userID, gameMode):
|
|
"""
|
|
Calculate accuracy value for userID relative to gameMode
|
|
|
|
:param userID: user id
|
|
:param gameMode: game mode number
|
|
:return: new accuracy
|
|
"""
|
|
# Select what to sort by
|
|
if gameMode == 0:
|
|
sortby = "pp"
|
|
else:
|
|
sortby = "accuracy"
|
|
# Get best accuracy scores
|
|
bestAccScores = glob.db.fetchAll(
|
|
"SELECT accuracy FROM scores WHERE userid = %s AND play_mode = %s AND completed = 3 ORDER BY " + sortby + " DESC LIMIT 500",
|
|
[userID, gameMode])
|
|
|
|
v = 0
|
|
if bestAccScores is not None:
|
|
# Calculate weighted accuracy
|
|
totalAcc = 0
|
|
divideTotal = 0
|
|
k = 0
|
|
for i in bestAccScores:
|
|
add = int((0.95 ** k) * 100)
|
|
totalAcc += i["accuracy"] * add
|
|
divideTotal += add
|
|
k += 1
|
|
# echo "$add - $totalacc - $divideTotal\n"
|
|
if divideTotal != 0:
|
|
v = totalAcc / divideTotal
|
|
else:
|
|
v = 0
|
|
return v
|
|
|
|
|
|
def calculateAccuracyRX(userID, gameMode):
|
|
"""
|
|
Calculate accuracy value for userID relative to gameMode
|
|
|
|
:param userID: user id
|
|
:param gameMode: game mode number
|
|
:return: new accuracy
|
|
"""
|
|
# Select what to sort by
|
|
if gameMode == 0:
|
|
sortby = "pp"
|
|
else:
|
|
sortby = "accuracy"
|
|
# Get best accuracy scores
|
|
bestAccScores = glob.db.fetchAll(
|
|
"SELECT accuracy FROM scores_relax WHERE userid = %s AND play_mode = %s AND completed = 3 ORDER BY " + sortby + " DESC LIMIT 500",
|
|
[userID, gameMode])
|
|
|
|
v = 0
|
|
if bestAccScores is not None:
|
|
# Calculate weighted accuracy
|
|
totalAcc = 0
|
|
divideTotal = 0
|
|
k = 0
|
|
for i in bestAccScores:
|
|
add = int((0.95 ** k) * 100)
|
|
totalAcc += i["accuracy"] * add
|
|
divideTotal += add
|
|
k += 1
|
|
# echo "$add - $totalacc - $divideTotal\n"
|
|
if divideTotal != 0:
|
|
v = totalAcc / divideTotal
|
|
else:
|
|
v = 0
|
|
return v
|
|
|
|
|
|
def calculateAccuracyAP(userID, gameMode):
|
|
"""
|
|
Calculate accuracy value for userID relative to gameMode
|
|
|
|
:param userID: user id
|
|
:param gameMode: game mode number
|
|
:return: new accuracy
|
|
"""
|
|
# Select what to sort by
|
|
if gameMode == 0:
|
|
sortby = "pp"
|
|
else:
|
|
sortby = "accuracy"
|
|
# Get best accuracy scores
|
|
bestAccScores = glob.db.fetchAll(
|
|
"SELECT accuracy FROM scores_auto WHERE userid = %s AND play_mode = %s AND completed = 3 ORDER BY " + sortby + " DESC LIMIT 500",
|
|
[userID, gameMode])
|
|
|
|
v = 0
|
|
if bestAccScores is not None:
|
|
# Calculate weighted accuracy
|
|
totalAcc = 0
|
|
divideTotal = 0
|
|
k = 0
|
|
for i in bestAccScores:
|
|
add = int((0.95 ** k) * 100)
|
|
totalAcc += i["accuracy"] * add
|
|
divideTotal += add
|
|
k += 1
|
|
# echo "$add - $totalacc - $divideTotal\n"
|
|
if divideTotal != 0:
|
|
v = totalAcc / divideTotal
|
|
else:
|
|
v = 0
|
|
return v
|
|
|
|
|
|
def calculatePP(userID, gameMode):
|
|
"""
|
|
Calculate userID's total PP for gameMode
|
|
|
|
:param userID: user id
|
|
:param gameMode: game mode number
|
|
:return: total PP
|
|
"""
|
|
# Get best pp scores
|
|
bestPPScores = glob.db.fetchAll(
|
|
"SELECT pp FROM scores WHERE userid = %s AND play_mode = %s AND completed = 3 ORDER BY pp DESC LIMIT 500",
|
|
[userID, gameMode])
|
|
|
|
# Calculate weighted PP
|
|
totalPP = 0
|
|
if bestPPScores is not None:
|
|
k = 0
|
|
for i in bestPPScores:
|
|
new = round(round(i["pp"]) * 0.95 ** k)
|
|
totalPP += new
|
|
k += 1
|
|
|
|
return totalPP
|
|
|
|
def calculatePPRelax(userID, gameMode):
|
|
"""
|
|
Calculate userID's total PP for gameMode
|
|
|
|
:param userID: user id
|
|
:param gameMode: game mode number
|
|
:return: total PP
|
|
"""
|
|
# Get best pp scores
|
|
bestPPScores = glob.db.fetchAll(
|
|
"SELECT pp FROM scores_relax WHERE userid = %s AND play_mode = %s AND completed = 3 ORDER BY pp DESC LIMIT 500",
|
|
[userID, gameMode])
|
|
|
|
# Calculate weighted PP
|
|
totalPP = 0
|
|
if bestPPScores is not None:
|
|
k = 0
|
|
for i in bestPPScores:
|
|
new = round(round(i["pp"]) * 0.95 ** k)
|
|
totalPP += new
|
|
k += 1
|
|
|
|
return totalPP
|
|
|
|
def calculatePPAuto(userID, gameMode):
|
|
"""
|
|
Calculate userID's total PP for gameMode
|
|
|
|
:param userID: user id
|
|
:param gameMode: game mode number
|
|
:return: total PP
|
|
"""
|
|
# Get best pp scores
|
|
bestPPScores = glob.db.fetchAll(
|
|
"SELECT pp FROM scores_auto WHERE userid = %s AND play_mode = %s AND completed = 3 ORDER BY pp DESC LIMIT 500",
|
|
[userID, gameMode])
|
|
|
|
# Calculate weighted PP
|
|
totalPP = 0
|
|
if bestPPScores is not None:
|
|
k = 0
|
|
for i in bestPPScores:
|
|
new = round(round(i["pp"]) * 0.95 ** k)
|
|
totalPP += new
|
|
k += 1
|
|
|
|
|
|
return totalPP
|
|
def updateAccuracy(userID, gameMode):
|
|
"""
|
|
Update accuracy value for userID relative to gameMode in DB
|
|
|
|
:param userID: user id
|
|
:param gameMode: gameMode number
|
|
:return:
|
|
"""
|
|
newAcc = calculateAccuracy(userID, gameMode)
|
|
mode = scoreUtils.readableGameMode(gameMode)
|
|
glob.db.execute("UPDATE users_stats SET avg_accuracy_{m} = %s WHERE id = %s LIMIT 1".format(m=mode),
|
|
[newAcc, userID])
|
|
|
|
def updateAccuracyRX(userID, gameMode):
|
|
"""
|
|
Update accuracy value for userID relative to gameMode in DB
|
|
|
|
:param userID: user id
|
|
:param gameMode: gameMode number
|
|
:return:
|
|
"""
|
|
newAcc = calculateAccuracyRX(userID, gameMode)
|
|
mode = scoreUtils.readableGameMode(gameMode)
|
|
glob.db.execute("UPDATE users_stats SET avg_accuracy_{m}_rx = %s WHERE id = %s LIMIT 1".format(m=mode),
|
|
[newAcc, userID])
|
|
|
|
def updateAccuracyAP(userID, gameMode):
|
|
"""
|
|
Update accuracy value for userID relative to gameMode in DB
|
|
|
|
:param userID: user id
|
|
:param gameMode: gameMode number
|
|
:return:
|
|
"""
|
|
newAcc = calculateAccuracyAP(userID, gameMode)
|
|
mode = scoreUtils.readableGameMode(gameMode)
|
|
glob.db.execute("UPDATE users_stats SET avg_accuracy_{m}_ap = %s WHERE id = %s LIMIT 1".format(m=mode),
|
|
[newAcc, userID])
|
|
|
|
def updatePP(userID, gameMode):
|
|
"""
|
|
Update userID's pp with new value
|
|
|
|
:param userID: user id
|
|
:param gameMode: game mode number
|
|
"""
|
|
# Make sure the user exists
|
|
# if not exists(userID):
|
|
# return
|
|
|
|
# Get new total PP and update db
|
|
newPP = calculatePP(userID, gameMode)
|
|
mode = scoreUtils.readableGameMode(gameMode)
|
|
glob.db.execute("UPDATE users_stats SET pp_{}=%s WHERE id = %s LIMIT 1".format(mode), [newPP, userID])
|
|
|
|
|
|
def updatePPRelax(userID, gameMode):
|
|
"""
|
|
Update userID's pp with new value
|
|
|
|
:param userID: user id
|
|
:param gameMode: game mode number
|
|
"""
|
|
# Make sure the user exists
|
|
# if not exists(userID):
|
|
# return
|
|
|
|
# Get new total PP and update db
|
|
newPP = calculatePPRelax(userID, gameMode)
|
|
mode = scoreUtils.readableGameMode(gameMode)
|
|
glob.db.execute("UPDATE users_stats SET pp_{}_rx=%s WHERE id = %s LIMIT 1".format(mode), [newPP, userID])
|
|
|
|
def updatePPAuto(userID, gameMode):
|
|
"""
|
|
Update userID's pp with new value
|
|
|
|
:param userID: user id
|
|
:param gameMode: game mode number
|
|
"""
|
|
# Make sure the user exists
|
|
# if not exists(userID):
|
|
# return
|
|
|
|
# Get new total PP and update db
|
|
newPP = calculatePPAuto(userID, gameMode)
|
|
mode = scoreUtils.readableGameMode(gameMode)
|
|
glob.db.execute("UPDATE users_stats SET pp_{}_auto=%s WHERE id = %s LIMIT 1".format(mode), [newPP, userID])
|
|
|
|
def updateStats(userID, __score):
|
|
"""
|
|
Update stats (playcount, total score, ranked score, level bla bla)
|
|
with data relative to a score object
|
|
|
|
:param userID:
|
|
:param __score: score object
|
|
"""
|
|
|
|
# Make sure the user exists
|
|
if not exists(userID):
|
|
log.warning("User {} doesn't exist.".format(userID))
|
|
return
|
|
|
|
# Get gamemode for db
|
|
mode = scoreUtils.readableGameMode(__score.gameMode)
|
|
|
|
# Update total score and playcount
|
|
glob.db.execute(
|
|
"UPDATE users_stats SET total_score_{m}=total_score_{m}+%s, playcount_{m}=playcount_{m}+1 WHERE id = %s LIMIT 1".format(
|
|
m=mode), [__score.score, userID])
|
|
|
|
# Calculate new level and update it
|
|
updateLevel(userID, __score.gameMode)
|
|
|
|
# Update level, accuracy and ranked score only if we have passed the song
|
|
if __score.passed:
|
|
# Update ranked score
|
|
glob.db.execute(
|
|
"UPDATE users_stats SET ranked_score_{m}=ranked_score_{m}+%s WHERE id = %s LIMIT 1".format(m=mode),
|
|
[__score.rankedScoreIncrease, userID])
|
|
|
|
# Update accuracy
|
|
updateAccuracy(userID, __score.gameMode)
|
|
|
|
# Update pp
|
|
updatePP(userID, __score.gameMode)
|
|
|
|
def updateStatsRx(userID, __score):
|
|
"""
|
|
Update stats (playcount, total score, ranked score, level bla bla)
|
|
with data relative to a score object
|
|
|
|
:param userID:
|
|
:param __score: score object
|
|
"""
|
|
|
|
# Make sure the user exists
|
|
if not exists(userID):
|
|
log.warning("User {} doesn't exist.".format(userID))
|
|
return
|
|
|
|
# Get gamemode for db
|
|
mode = scoreUtils.readableGameMode(__score.gameMode)
|
|
|
|
# Update total score and playcount
|
|
glob.db.execute(
|
|
"UPDATE users_stats SET total_score_{m}_rx=total_score_{m}_rx+%s, playcount_{m}_rx=playcount_{m}_rx+1 WHERE id = %s LIMIT 1".format(
|
|
m=mode), [__score.score, userID])
|
|
|
|
# Calculate new level and update it
|
|
updateLevelRX(userID, __score.gameMode)
|
|
|
|
# Update level, accuracy and ranked score only if we have passed the song
|
|
if __score.passed:
|
|
# Update ranked score
|
|
glob.db.execute(
|
|
"UPDATE users_stats SET ranked_score_{m}_rx=ranked_score_{m}_rx+%s WHERE id = %s LIMIT 1".format(m=mode),
|
|
[__score.rankedScoreIncrease, userID])
|
|
|
|
# Update accuracy
|
|
updateAccuracyRX(userID, __score.gameMode)
|
|
|
|
# Update pp
|
|
updatePPRelax(userID, __score.gameMode)
|
|
|
|
def updateStatsAp(userID, __score):
|
|
"""
|
|
Update stats (playcount, total score, ranked score, level bla bla)
|
|
with data relative to a score object
|
|
|
|
:param userID:
|
|
:param __score: score object
|
|
"""
|
|
|
|
# Make sure the user exists
|
|
if not exists(userID):
|
|
log.warning("User {} doesn't exist.".format(userID))
|
|
return
|
|
|
|
# Get gamemode for db
|
|
mode = scoreUtils.readableGameMode(__score.gameMode)
|
|
|
|
|
|
# Update total score and playcount
|
|
glob.db.execute(
|
|
"UPDATE users_stats SET total_score_{m}_ap=total_score_{m}_a-+%s, playcount_{m}_ap=playcount_{m}_ap+1 WHERE id = %s LIMIT 1".format(
|
|
m=mode), [__score.score, userID])
|
|
|
|
# Calculate new level and update it
|
|
updateLevelAP(userID, __score.gameMode)
|
|
|
|
# Update level, accuracy and ranked score only if we have passed the song
|
|
if __score.passed:
|
|
# Update ranked score
|
|
glob.db.execute(
|
|
"UPDATE users_stats SET ranked_score_{m}=ranked_score_{m}+%s WHERE id = %s LIMIT 1".format(m=mode),
|
|
[__score.rankedScoreIncrease, userID])
|
|
|
|
# Update accuracy
|
|
updateAccuracyAP(userID, __score.gameMode)
|
|
|
|
# Update pp
|
|
updatePPAuto(userID, __score.gameMode)
|
|
def updateLatestActivity(userID):
|
|
"""
|
|
Update userID's latest activity to current UNIX time
|
|
|
|
:param userID: user id
|
|
:return:
|
|
"""
|
|
glob.db.execute("UPDATE users SET latest_activity = %s WHERE id = %s LIMIT 1", [int(time.time()), userID])
|
|
|
|
def getRankedScore(userID, gameMode):
|
|
"""
|
|
Get userID's ranked score relative to gameMode
|
|
|
|
:param userID: user id
|
|
:param gameMode: game mode number
|
|
:return: ranked score
|
|
"""
|
|
mode = scoreUtils.readableGameMode(gameMode)
|
|
result = glob.db.fetch("SELECT ranked_score_{} FROM users_stats WHERE id = %s LIMIT 1".format(mode), [userID])
|
|
if result is not None:
|
|
return result["ranked_score_{}".format(mode)]
|
|
else:
|
|
return 0
|
|
|
|
def getPP(userID, gameMode):
|
|
"""
|
|
Get userID's PP relative to gameMode
|
|
|
|
:param userID: user id
|
|
:param gameMode: game mode number
|
|
:return: pp
|
|
"""
|
|
|
|
mode = scoreUtils.readableGameMode(gameMode)
|
|
result = glob.db.fetch("SELECT pp_{} FROM users_stats WHERE id = %s LIMIT 1".format(mode), [userID])
|
|
if result is not None:
|
|
return result["pp_{}".format(mode)]
|
|
else:
|
|
return 0
|
|
|
|
def incrementReplaysWatched(userID, gameMode):
|
|
"""
|
|
Increment userID's replays watched by others relative to gameMode
|
|
|
|
:param userID: user id
|
|
:param gameMode: game mode number
|
|
:return:
|
|
"""
|
|
mode = scoreUtils.readableGameMode(gameMode)
|
|
glob.db.execute(
|
|
"UPDATE users_stats SET replays_watched_{mode}=replays_watched_{mode}+1 WHERE id = %s LIMIT 1".format(
|
|
mode=mode), [userID])
|
|
|
|
|
|
def getAqn(userID):
|
|
"""
|
|
Check if AQN folder was detected for userID
|
|
|
|
:param userID: user
|
|
:return: True if hax, False if legit
|
|
"""
|
|
result = glob.db.fetch("SELECT aqn FROM users WHERE id = %s LIMIT 1", [userID])
|
|
if result is not None:
|
|
return True if int(result["aqn"]) == 1 else False
|
|
else:
|
|
return False
|
|
|
|
def setAqn(userID, value=1):
|
|
"""
|
|
Set AQN folder status for userID
|
|
|
|
:param userID: user
|
|
:param value: new aqn value, default = 1
|
|
:return:
|
|
"""
|
|
glob.db.fetch("UPDATE users SET aqn = %s WHERE id = %s LIMIT 1", [value, userID])
|
|
|
|
def IPLog(userID, ip):
|
|
"""
|
|
Log user IP
|
|
|
|
:param userID: user id
|
|
:param ip: IP address
|
|
:return:
|
|
"""
|
|
glob.db.execute("""INSERT INTO ip_user (userid, ip, occurencies) VALUES (%s, %s, '1')
|
|
ON DUPLICATE KEY UPDATE occurencies = occurencies + 1""", [userID, ip])
|
|
|
|
def checkBanchoSession(userID, ip=""):
|
|
"""
|
|
Return True if there is a bancho session for `userID` from `ip`
|
|
If `ip` is an empty string, check if there's a bancho session for that user, from any IP.
|
|
|
|
:param userID: user id
|
|
:param ip: ip address. Optional. Default: empty string
|
|
:return: True if there's an active bancho session, else False
|
|
"""
|
|
if ip != "":
|
|
return glob.redis.sismember("peppy:sessions:{}".format(userID), ip)
|
|
else:
|
|
return glob.redis.exists("peppy:sessions:{}".format(userID))
|
|
|
|
def is2FAEnabled(userID):
|
|
"""
|
|
Returns True if 2FA/Google auth 2FA is enable for `userID`
|
|
|
|
:userID: user ID
|
|
:return: True if 2fa is enabled, else False
|
|
"""
|
|
return glob.db.fetch("SELECT 2fa_totp.userid FROM 2fa_totp WHERE userid = %(userid)s AND enabled = 1 LIMIT 1", {
|
|
"userid": userID
|
|
}) is not None
|
|
|
|
def check2FA(userID, ip):
|
|
"""
|
|
Returns True if this IP is untrusted.
|
|
Returns always False if 2fa is not enabled on `userID`
|
|
|
|
:param userID: user id
|
|
:param ip: IP address
|
|
:return: True if untrusted, False if trusted or 2fa is disabled.
|
|
"""
|
|
if not is2FAEnabled(userID):
|
|
return False
|
|
|
|
result = glob.db.fetch("SELECT id FROM ip_user WHERE userid = %s AND ip = %s", [userID, ip])
|
|
return True if result is None else False
|
|
|
|
def isAllowed(userID):
|
|
"""
|
|
Check if userID is not banned or restricted
|
|
|
|
:param userID: user id
|
|
:return: True if not banned or restricted, otherwise false.
|
|
"""
|
|
result = glob.db.fetch("SELECT privileges FROM users WHERE id = %s LIMIT 1", [userID])
|
|
if result is not None:
|
|
return (result["privileges"] & privileges.USER_NORMAL) and (result["privileges"] & privileges.USER_PUBLIC)
|
|
else:
|
|
return False
|
|
|
|
def isRestricted(userID):
|
|
"""
|
|
Check if userID is restricted
|
|
|
|
:param userID: user id
|
|
:return: True if not restricted, otherwise false.
|
|
"""
|
|
result = glob.db.fetch("SELECT privileges FROM users WHERE id = %s LIMIT 1", [userID])
|
|
if result is not None:
|
|
return (result["privileges"] & privileges.USER_NORMAL) and not (result["privileges"] & privileges.USER_PUBLIC)
|
|
else:
|
|
return False
|
|
|
|
def isBanned(userID):
|
|
"""
|
|
Check if userID is banned
|
|
|
|
:param userID: user id
|
|
:return: True if not banned, otherwise false.
|
|
"""
|
|
result = glob.db.fetch("SELECT privileges FROM users WHERE id = %s LIMIT 1", [userID])
|
|
if result is not None:
|
|
return not (result["privileges"] & 3 > 0)
|
|
else:
|
|
return True
|
|
|
|
def isLocked(userID):
|
|
"""
|
|
Check if userID is locked
|
|
|
|
:param userID: user id
|
|
:return: True if not locked, otherwise false.
|
|
"""
|
|
result = glob.db.fetch("SELECT privileges FROM users WHERE id = %s LIMIT 1", [userID])
|
|
if result is not None:
|
|
return (
|
|
(result["privileges"] & privileges.USER_PUBLIC > 0) and (result["privileges"] & privileges.USER_NORMAL == 0))
|
|
else:
|
|
return True
|
|
|
|
def ban(userID):
|
|
"""
|
|
Ban userID
|
|
|
|
:param userID: user id
|
|
:return:
|
|
"""
|
|
# Set user as banned in db
|
|
banDateTime = int(time.time())
|
|
glob.db.execute("UPDATE users SET privileges = privileges & %s, ban_datetime = %s WHERE id = %s LIMIT 1",
|
|
[~(privileges.USER_NORMAL | privileges.USER_PUBLIC), banDateTime, userID])
|
|
|
|
# Notify bancho about the ban
|
|
glob.redis.publish("peppy:ban", userID)
|
|
|
|
# Remove the user from global and country leaderboards
|
|
removeFromLeaderboard(userID)
|
|
|
|
def unban(userID):
|
|
"""
|
|
Unban userID
|
|
|
|
:param userID: user id
|
|
:return:
|
|
"""
|
|
glob.db.execute("UPDATE users SET privileges = privileges | %s, ban_datetime = 0 WHERE id = %s LIMIT 1",
|
|
[(privileges.USER_NORMAL | privileges.USER_PUBLIC), userID])
|
|
glob.redis.publish("peppy:ban", userID)
|
|
|
|
def restrict(userID):
|
|
"""
|
|
Restrict userID
|
|
|
|
:param userID: user id
|
|
:return:
|
|
"""
|
|
if not isRestricted(userID):
|
|
# Set user as restricted in db
|
|
banDateTime = int(time.time())
|
|
glob.db.execute("UPDATE users SET privileges = privileges & %s, ban_datetime = %s WHERE id = %s LIMIT 1",
|
|
[~privileges.USER_PUBLIC, banDateTime, userID])
|
|
|
|
# Notify bancho about this ban
|
|
glob.redis.publish("peppy:ban", userID)
|
|
|
|
# Remove the user from global and country leaderboards
|
|
removeFromLeaderboard(userID)
|
|
|
|
def unrestrict(userID):
|
|
"""
|
|
Unrestrict userID.
|
|
Same as unban().
|
|
|
|
:param userID: user id
|
|
:return:
|
|
"""
|
|
unban(userID)
|
|
|
|
def appendNotes(userID, notes, addNl=True, trackDate=True):
|
|
"""
|
|
Append `notes` to `userID`'s "notes for CM"
|
|
|
|
:param userID: user id
|
|
:param notes: text to append
|
|
:param addNl: if True, prepend \n to notes. Default: True.
|
|
:param trackDate: if True, prepend date and hour to the note. Default: True.
|
|
:return:
|
|
"""
|
|
if trackDate:
|
|
notes = "[{}] {}".format(generalUtils.getTimestamp(), notes)
|
|
if addNl:
|
|
notes = "\n{}".format(notes)
|
|
glob.db.execute("UPDATE users SET notes=CONCAT(COALESCE(notes, ''),%s) WHERE id = %s LIMIT 1", [notes, userID])
|
|
|
|
def getPrivileges(userID):
|
|
"""
|
|
Return `userID`'s privileges
|
|
|
|
:param userID: user id
|
|
:return: privileges number
|
|
"""
|
|
result = glob.db.fetch("SELECT privileges FROM users WHERE id = %s LIMIT 1", [userID])
|
|
if result is not None:
|
|
return result["privileges"]
|
|
else:
|
|
return 0
|
|
|
|
def getSilenceEnd(userID):
|
|
"""
|
|
Get userID's **ABSOLUTE** silence end UNIX time
|
|
Remember to subtract time.time() if you want to get the actual silence time
|
|
|
|
:param userID: user id
|
|
:return: UNIX time
|
|
"""
|
|
return glob.db.fetch("SELECT silence_end FROM users WHERE id = %s LIMIT 1", [userID])["silence_end"]
|
|
|
|
def silence(userID, seconds, silenceReason, author = 999):
|
|
"""
|
|
Silence someone
|
|
|
|
:param userID: user id
|
|
:param seconds: silence length in seconds
|
|
:param silenceReason: silence reason shown on website
|
|
:param author: userID of who silenced the user. Default: 999
|
|
:return:
|
|
"""
|
|
# db qurey
|
|
silenceEndTime = int(time.time())+seconds
|
|
glob.db.execute("UPDATE users SET silence_end = %s, silence_reason = %s WHERE id = %s LIMIT 1", [silenceEndTime, silenceReason, userID])
|
|
|
|
# Log
|
|
targetUsername = getUsername(userID)
|
|
# TODO: exists check im drunk rn i need to sleep (stampa piede ubriaco confirmed)
|
|
if seconds > 0:
|
|
log.rap(author, "has silenced {} for {} seconds for the following reason: \"{}\"".format(targetUsername, seconds, silenceReason), True)
|
|
else:
|
|
log.rap(author, "has removed {}'s silence".format(targetUsername), True)
|
|
|
|
def getTotalScore(userID, gameMode):
|
|
"""
|
|
Get `userID`'s total score relative to `gameMode`
|
|
|
|
:param userID: user id
|
|
:param gameMode: game mode number
|
|
:return: total score
|
|
"""
|
|
modeForDB = gameModes.getGameModeForDB(gameMode)
|
|
return glob.db.fetch("SELECT total_score_"+modeForDB+" FROM users_stats WHERE id = %s LIMIT 1", [userID])["total_score_"+modeForDB]
|
|
|
|
def getAccuracy(userID, gameMode):
|
|
"""
|
|
Get `userID`'s average accuracy relative to `gameMode`
|
|
|
|
:param userID: user id
|
|
:param gameMode: game mode number
|
|
:return: accuracy
|
|
"""
|
|
modeForDB = gameModes.getGameModeForDB(gameMode)
|
|
return glob.db.fetch("SELECT avg_accuracy_"+modeForDB+" FROM users_stats WHERE id = %s LIMIT 1", [userID])["avg_accuracy_"+modeForDB]
|
|
|
|
def getGameRank(userID, gameMode):
|
|
"""
|
|
Get `userID`'s **in-game rank** (eg: #1337) relative to gameMode
|
|
|
|
:param userID: user id
|
|
:param gameMode: game mode number
|
|
:return: game rank
|
|
"""
|
|
position = glob.redis.zrevrank("ripple:leaderboard:{}".format(gameModes.getGameModeForDB(gameMode)), userID)
|
|
if position is None:
|
|
return 0
|
|
else:
|
|
return int(position) + 1
|
|
|
|
def getGameRankRx(userID, gameMode):
|
|
"""
|
|
Get `userID`'s **in-game rank** (eg: #1337) relative to gameMode
|
|
:param userID: user id
|
|
:param gameMode: game mode number
|
|
:return: game rank
|
|
"""
|
|
position = glob.redis.zrevrank("ripple:leaderboard_relax:{}".format(gameModes.getGameModeForDB(gameMode)), userID)
|
|
if position is None:
|
|
return 0
|
|
else:
|
|
return int(position) + 1
|
|
|
|
def getGameRankAp(userID, gameMode):
|
|
"""
|
|
Get `userID`'s **in-game rank** (eg: #1337) relative to gameMode
|
|
:param userID: user id
|
|
:param gameMode: game mode number
|
|
:return: game rank
|
|
"""
|
|
position = glob.redis.zrevrank("ripple:leaderboard_auto:{}".format(gameModes.getGameModeForDB(gameMode)), userID)
|
|
if position is None:
|
|
return 0
|
|
else:
|
|
return int(position) + 1
|
|
|
|
def getPlaycount(userID, gameMode):
|
|
"""
|
|
Get `userID`'s playcount relative to `gameMode`
|
|
|
|
:param userID: user id
|
|
:param gameMode: game mode number
|
|
:return: playcount
|
|
"""
|
|
modeForDB = gameModes.getGameModeForDB(gameMode)
|
|
return glob.db.fetch("SELECT playcount_"+modeForDB+" FROM users_stats WHERE id = %s LIMIT 1", [userID])["playcount_"+modeForDB]
|
|
|
|
def getPlaycountRX(userID, gameMode):
|
|
"""
|
|
Get `userID`'s playcount relative to `gameMode`
|
|
|
|
:param userID: user id
|
|
:param gameMode: game mode number
|
|
:return: playcount
|
|
"""
|
|
modeForDB = gameModes.getGameModeForDB(gameMode)
|
|
return glob.db.fetch("SELECT playcount_"+modeForDB+"_rx FROM users_stats WHERE id = %s LIMIT 1", [userID])["playcount_"+modeForDB]
|
|
|
|
|
|
def getPlaycountAP(userID, gameMode):
|
|
"""
|
|
Get `userID`'s playcount relative to `gameMode`
|
|
|
|
:param userID: user id
|
|
:param gameMode: game mode number
|
|
:return: playcount
|
|
"""
|
|
modeForDB = gameModes.getGameModeForDB(gameMode)
|
|
return glob.db.fetch("SELECT playcount_"+modeForDB+"_ap FROM users_stats WHERE id = %s LIMIT 1", [userID])["playcount_"+modeForDB]
|
|
|
|
|
|
def getFriendList(userID):
|
|
"""
|
|
Get `userID`'s friendlist
|
|
|
|
:param userID: user id
|
|
:return: list with friends userIDs. [0] if no friends.
|
|
"""
|
|
# Get friends from db
|
|
friends = glob.db.fetchAll("SELECT user2 FROM users_relationships WHERE user1 = %s", [userID])
|
|
|
|
if friends is None or len(friends) == 0:
|
|
# We have no friends, return 0 list
|
|
return [0]
|
|
else:
|
|
# Get only friends
|
|
friends = [i["user2"] for i in friends]
|
|
|
|
# Return friend IDs
|
|
return friends
|
|
|
|
def addFriend(userID, friendID):
|
|
"""
|
|
Add `friendID` to `userID`'s friend list
|
|
|
|
:param userID: user id
|
|
:param friendID: new friend
|
|
:return:
|
|
"""
|
|
# Make sure we aren't adding us to our friends
|
|
if userID == friendID:
|
|
return
|
|
|
|
# check user isn't already a friend of ours
|
|
if glob.db.fetch("SELECT id FROM users_relationships WHERE user1 = %s AND user2 = %s LIMIT 1", [userID, friendID]) is not None:
|
|
return
|
|
|
|
# Set new value
|
|
glob.db.execute("INSERT INTO users_relationships (user1, user2) VALUES (%s, %s)", [userID, friendID])
|
|
|
|
def removeFriend(userID, friendID):
|
|
"""
|
|
Remove `friendID` from `userID`'s friend list
|
|
|
|
:param userID: user id
|
|
:param friendID: old friend
|
|
:return:
|
|
"""
|
|
# Delete user relationship. We don't need to check if the relationship was there, because who gives a shit,
|
|
# if they were not friends and they don't want to be anymore, be it. ¯\_(ツ)_/¯
|
|
# TODO: LIMIT 1
|
|
glob.db.execute("DELETE FROM users_relationships WHERE user1 = %s AND user2 = %s", [userID, friendID])
|
|
|
|
|
|
def getCountry(userID):
|
|
"""
|
|
Get `userID`'s country **(two letters)**.
|
|
|
|
:param userID: user id
|
|
:return: country code (two letters)
|
|
"""
|
|
return glob.db.fetch("SELECT country FROM users_stats WHERE id = %s LIMIT 1", [userID])["country"]
|
|
|
|
def setCountry(userID, country):
|
|
"""
|
|
Set userID's country
|
|
|
|
:param userID: user id
|
|
:param country: country letters
|
|
:return:
|
|
"""
|
|
glob.db.execute("UPDATE users_stats SET country = %s WHERE id = %s LIMIT 1", [country, userID])
|
|
|
|
def logIP(userID, ip):
|
|
"""
|
|
User IP log
|
|
USED FOR MULTIACCOUNT DETECTION
|
|
|
|
:param userID: user id
|
|
:param ip: IP address
|
|
:return:
|
|
"""
|
|
glob.db.execute("""INSERT INTO ip_user (userid, ip, occurencies) VALUES (%s, %s, 1)
|
|
ON DUPLICATE KEY UPDATE occurencies = occurencies + 1""", [userID, ip])
|
|
|
|
def saveBanchoSession(userID, ip):
|
|
"""
|
|
Save userid and ip of this token in redis
|
|
Used to cache logins on LETS requests
|
|
|
|
:param userID: user ID
|
|
:param ip: IP address
|
|
:return:
|
|
"""
|
|
glob.redis.sadd("peppy:sessions:{}".format(userID), ip)
|
|
|
|
def deleteBanchoSessions(userID, ip):
|
|
"""
|
|
Delete this bancho session from redis
|
|
|
|
:param userID: user id
|
|
:param ip: IP address
|
|
:return:
|
|
"""
|
|
glob.redis.srem("peppy:sessions:{}".format(userID), ip)
|
|
|
|
def setPrivileges(userID, priv):
|
|
"""
|
|
Set userID's privileges in db
|
|
|
|
:param userID: user id
|
|
:param priv: privileges number
|
|
:return:
|
|
"""
|
|
glob.db.execute("UPDATE users SET privileges = %s WHERE id = %s LIMIT 1", [priv, userID])
|
|
|
|
def getGroupPrivileges(groupName):
|
|
"""
|
|
Returns the privileges number of a group, by its name
|
|
|
|
:param groupName: name of the group
|
|
:return: privilege integer or `None` if the group doesn't exist
|
|
"""
|
|
groupPrivileges = glob.db.fetch("SELECT privileges FROM privileges_groups WHERE name = %s LIMIT 1", [groupName])
|
|
if groupPrivileges is None:
|
|
return None
|
|
return groupPrivileges["privileges"]
|
|
|
|
def isInPrivilegeGroup(userID, groupName):
|
|
"""
|
|
Check if `userID` is in a privilege group.
|
|
Donor privilege is ignored while checking for groups.
|
|
|
|
:param userID: user id
|
|
:param groupName: privilege group name
|
|
:return: True if `userID` is in `groupName`, else False
|
|
"""
|
|
groupPrivileges = getGroupPrivileges(groupName)
|
|
if groupPrivileges is None:
|
|
return False
|
|
try:
|
|
userToken = glob.tokens.getTokenFromUserID(userID)
|
|
except AttributeError:
|
|
# LETS compatibility
|
|
userToken = None
|
|
|
|
if userToken is not None:
|
|
userPrivileges = userToken.privileges
|
|
else:
|
|
userPrivileges = getPrivileges(userID)
|
|
return userPrivileges & groupPrivileges == groupPrivileges
|
|
|
|
def isInAnyPrivilegeGroup(userID, groups):
|
|
"""
|
|
Checks if a user is in at least one of the specified groups
|
|
|
|
:param userID: id of the user
|
|
:param groups: groups list or tuple
|
|
:return: `True` if `userID` is in at least one of the specified groups, otherwise `False`
|
|
"""
|
|
userPrivileges = getPrivileges(userID)
|
|
return any(
|
|
userPrivileges & x == x
|
|
for x in (
|
|
getGroupPrivileges(y) for y in groups
|
|
) if x is not None
|
|
)
|
|
|
|
def logHardware(userID, hashes, activation = False):
|
|
"""
|
|
Hardware log
|
|
USED FOR MULTIACCOUNT DETECTION
|
|
|
|
|
|
:param userID: user id
|
|
:param hashes: Peppy's botnet (client data) structure (new line = "|", already split)
|
|
[0] osu! version
|
|
[1] plain mac addressed, separated by "."
|
|
[2] mac addresses hash set
|
|
[3] unique ID
|
|
[4] disk ID
|
|
:param activation: if True, set this hash as used for activation. Default: False.
|
|
:return: True if hw is not banned, otherwise false
|
|
"""
|
|
# Make sure the strings are not empty
|
|
for i in hashes[2:5]:
|
|
if i == "":
|
|
log.warning("Invalid hash set ({}) for user {} in HWID check".format(hashes, userID), "bunk")
|
|
return False
|
|
|
|
# Run some HWID checks on that user if he is not restricted
|
|
if not isRestricted(userID):
|
|
# Get username
|
|
username = getUsername(userID)
|
|
|
|
# Get the list of banned or restricted users that have logged in from this or similar HWID hash set
|
|
if hashes[2] == "b4ec3c4334a0249dae95c284ec5983df":
|
|
# Running under wine, check by unique id
|
|
log.debug("Logging Linux/Mac hardware")
|
|
banned = glob.db.fetchAll("""SELECT users.id as userid, hw_user.occurencies, users.username FROM hw_user
|
|
LEFT JOIN users ON users.id = hw_user.userid
|
|
WHERE hw_user.userid != %(userid)s
|
|
AND hw_user.unique_id = %(uid)s
|
|
AND (users.privileges & 3 != 3)""", {
|
|
"userid": userID,
|
|
"uid": hashes[3],
|
|
})
|
|
else:
|
|
# Running under windows, do all checks
|
|
log.debug("Logging Windows hardware")
|
|
banned = glob.db.fetchAll("""SELECT users.id as userid, hw_user.occurencies, users.username FROM hw_user
|
|
LEFT JOIN users ON users.id = hw_user.userid
|
|
WHERE hw_user.userid != %(userid)s
|
|
AND hw_user.mac = %(mac)s
|
|
AND hw_user.unique_id = %(uid)s
|
|
AND hw_user.disk_id = %(diskid)s
|
|
AND (users.privileges & 3 != 3)""", {
|
|
"userid": userID,
|
|
"mac": hashes[2],
|
|
"uid": hashes[3],
|
|
"diskid": hashes[4],
|
|
})
|
|
|
|
for i in banned:
|
|
# Get the total numbers of logins
|
|
total = glob.db.fetch("SELECT COUNT(*) AS count FROM hw_user WHERE userid = %s LIMIT 1", [userID])
|
|
# and make sure it is valid
|
|
if total is None:
|
|
continue
|
|
total = total["count"]
|
|
|
|
# Calculate 10% of total
|
|
perc = (total*10)/100
|
|
|
|
if i["occurencies"] >= perc:
|
|
# If the banned user has logged in more than 10% of the times from this user, restrict this user
|
|
restrict(userID)
|
|
appendNotes(userID, "Logged in from HWID ({hwid}) used more than 10% from user {banned} ({bannedUserID}), who is banned/restricted.".format(
|
|
hwid=hashes[2:5],
|
|
banned=i["username"],
|
|
bannedUserID=i["userid"]
|
|
))
|
|
log.warning("**{user}** ({userID}) has been restricted because he has logged in from HWID _({hwid})_ used more than 10% from banned/restricted user **{banned}** ({bannedUserID}), **possible multiaccount**.".format(
|
|
user=username,
|
|
userID=userID,
|
|
hwid=hashes[2:5],
|
|
banned=i["username"],
|
|
bannedUserID=i["userid"]
|
|
), "cm")
|
|
|
|
# Update hash set occurencies
|
|
glob.db.execute("""
|
|
INSERT INTO hw_user (id, userid, mac, unique_id, disk_id, occurencies) VALUES (NULL, %s, %s, %s, %s, 1)
|
|
ON DUPLICATE KEY UPDATE occurencies = occurencies + 1
|
|
""", [userID, hashes[2], hashes[3], hashes[4]])
|
|
|
|
# Optionally, set this hash as 'used for activation'
|
|
if activation:
|
|
glob.db.execute("UPDATE hw_user SET activated = 1 WHERE userid = %s AND mac = %s AND unique_id = %s AND disk_id = %s", [userID, hashes[2], hashes[3], hashes[4]])
|
|
|
|
# Access granted, abbiamo impiegato 3 giorni
|
|
# We grant access even in case of login from banned HWID
|
|
# because we call restrict() above so there's no need to deny the access.
|
|
return True
|
|
|
|
|
|
def resetPendingFlag(userID, success=True):
|
|
"""
|
|
Remove pending flag from an user.
|
|
|
|
:param userID: user id
|
|
:param success: if True, set USER_PUBLIC and USER_NORMAL flags too
|
|
"""
|
|
glob.db.execute("UPDATE users SET privileges = privileges & %s WHERE id = %s LIMIT 1", [~privileges.USER_PENDING_VERIFICATION, userID])
|
|
if success:
|
|
glob.db.execute("UPDATE users SET privileges = privileges | %s WHERE id = %s LIMIT 1", [(privileges.USER_PUBLIC | privileges.USER_NORMAL), userID])
|
|
|
|
def verifyUser(userID, hashes):
|
|
"""
|
|
Activate `userID`'s account.
|
|
|
|
:param userID: user id
|
|
:param hashes: Peppy's botnet (client data) structure (new line = "|", already split)
|
|
[0] osu! version
|
|
[1] plain mac addressed, separated by "."
|
|
[2] mac addresses hash set
|
|
[3] unique ID
|
|
[4] disk ID
|
|
:return: True if verified successfully, else False (multiaccount)
|
|
"""
|
|
# Check for valid hash set
|
|
for i in hashes[2:5]:
|
|
if i == "":
|
|
log.warning("Invalid hash set ({}) for user {} while verifying the account".format(str(hashes), userID), "bunk")
|
|
return False
|
|
|
|
# Get username
|
|
username = getUsername(userID)
|
|
|
|
# Make sure there are no other accounts activated with this exact mac/unique id/hwid
|
|
if hashes[2] == "b4ec3c4334a0249dae95c284ec5983df" or hashes[4] == "ffae06fb022871fe9beb58b005c5e21d":
|
|
# Running under wine, check only by uniqueid
|
|
log.info("{user} ({userID}) ha triggerato Sannino:\n**Full data:** {hashes}\n**Usual wine mac address hash:** b4ec3c4334a0249dae95c284ec5983df\n**Usual wine disk id:** ffae06fb022871fe9beb58b005c5e21d".format(user=username, userID=userID, hashes=hashes), "bunker")
|
|
log.debug("Veryfing with Linux/Mac hardware")
|
|
match = glob.db.fetchAll("SELECT userid FROM hw_user WHERE unique_id = %(uid)s AND userid != %(userid)s AND activated = 1 LIMIT 1", {
|
|
"uid": hashes[3],
|
|
"userid": userID
|
|
})
|
|
else:
|
|
# Running under windows, full check
|
|
log.debug("Veryfing with Windows hardware")
|
|
match = glob.db.fetchAll("SELECT userid FROM hw_user WHERE mac = %(mac)s AND unique_id = %(uid)s AND disk_id = %(diskid)s AND userid != %(userid)s AND activated = 1 LIMIT 1", {
|
|
"mac": hashes[2],
|
|
"uid": hashes[3],
|
|
"diskid": hashes[4],
|
|
"userid": userID
|
|
})
|
|
|
|
if match:
|
|
# This is a multiaccount, restrict other account and ban this account
|
|
|
|
# Get original userID and username (lowest ID)
|
|
originalUserID = match[0]["userid"]
|
|
originalUsername = getUsername(originalUserID)
|
|
|
|
# Ban this user and append notes
|
|
ban(userID) # this removes the USER_PENDING_VERIFICATION flag too
|
|
appendNotes(userID, "{}'s multiaccount ({}), found HWID match while verifying account ({})".format(originalUsername, originalUserID, hashes[2:5]))
|
|
appendNotes(originalUserID, "Has created multiaccount {} ({})".format(username, userID))
|
|
|
|
# Restrict the original
|
|
restrict(originalUserID)
|
|
|
|
# Discord message
|
|
log.warning("User **{originalUsername}** ({originalUserID}) has been restricted because he has created multiaccount **{username}** ({userID}). The multiaccount has been banned.".format(
|
|
originalUsername=originalUsername,
|
|
originalUserID=originalUserID,
|
|
username=username,
|
|
userID=userID
|
|
), "cm")
|
|
|
|
# Disallow login
|
|
return False
|
|
else:
|
|
# No matches found, set USER_PUBLIC and USER_NORMAL flags and reset USER_PENDING_VERIFICATION flag
|
|
resetPendingFlag(userID)
|
|
#log.info("User **{}** ({}) has verified his account with hash set _{}_".format(username, userID, hashes[2:5]), "cm")
|
|
|
|
# Allow login
|
|
return True
|
|
|
|
def hasVerifiedHardware(userID):
|
|
"""
|
|
Checks if `userID` has activated his account through HWID
|
|
|
|
:param userID: user id
|
|
:return: True if hwid activation data is in db, otherwise False
|
|
"""
|
|
data = glob.db.fetch("SELECT id FROM hw_user WHERE userid = %s AND activated = 1 LIMIT 1", [userID])
|
|
if data is not None:
|
|
return True
|
|
return False
|
|
|
|
def getDonorExpire(userID):
|
|
"""
|
|
Return `userID`'s donor expiration UNIX timestamp
|
|
|
|
:param userID: user id
|
|
:return: donor expiration UNIX timestamp
|
|
"""
|
|
data = glob.db.fetch("SELECT donor_expire FROM users WHERE id = %s LIMIT 1", [userID])
|
|
if data is not None:
|
|
return data["donor_expire"]
|
|
return 0
|
|
|
|
|
|
class invalidUsernameError(Exception):
|
|
pass
|
|
|
|
class usernameAlreadyInUseError(Exception):
|
|
pass
|
|
|
|
def safeUsername(username):
|
|
"""
|
|
Return `username`'s safe username
|
|
(all lowercase and underscores instead of spaces)
|
|
|
|
:param username: unsafe username
|
|
:return: safe username
|
|
"""
|
|
return username.lower().strip().replace(" ", "_")
|
|
|
|
def changeUsername(userID=0, oldUsername="", newUsername=""):
|
|
"""
|
|
Change `userID`'s username to `newUsername` in database
|
|
|
|
:param userID: user id. Required only if `oldUsername` is not passed.
|
|
:param oldUsername: username. Required only if `userID` is not passed.
|
|
:param newUsername: new username. Can't contain spaces and underscores at the same time.
|
|
:raise: invalidUsernameError(), usernameAlreadyInUseError()
|
|
:return:
|
|
"""
|
|
# Make sure new username doesn't have mixed spaces and underscores
|
|
if " " in newUsername and "_" in newUsername:
|
|
raise invalidUsernameError()
|
|
|
|
# Get safe username
|
|
newUsernameSafe = safeUsername(newUsername)
|
|
|
|
# Make sure this username is not already in use
|
|
if getIDSafe(newUsernameSafe) is not None:
|
|
raise usernameAlreadyInUseError()
|
|
|
|
# Get userID or oldUsername
|
|
if userID == 0:
|
|
userID = getID(oldUsername)
|
|
else:
|
|
oldUsername = getUsername(userID)
|
|
|
|
# Change username
|
|
glob.db.execute("UPDATE users SET username = %s, username_safe = %s WHERE id = %s LIMIT 1", [newUsername, newUsernameSafe, userID])
|
|
glob.db.execute("UPDATE users_stats SET username = %s WHERE id = %s LIMIT 1", [newUsername, userID])
|
|
|
|
# Empty redis username cache
|
|
# TODO: Le pipe woo woo
|
|
glob.redis.delete("ripple:userid_cache:{}".format(safeUsername(oldUsername)))
|
|
glob.redis.delete("ripple:change_username_pending:{}".format(userID))
|
|
|
|
def removeFromLeaderboard(userID):
|
|
"""
|
|
Removes userID from global and country leaderboards.
|
|
|
|
:param userID:
|
|
:return:
|
|
"""
|
|
# Remove the user from global and country leaderboards, for every mode
|
|
country = getCountry(userID).lower()
|
|
for mode in ["std", "taiko", "ctb", "mania"]:
|
|
glob.redis.zrem("ripple:leaderboard:{}".format(mode), str(userID))
|
|
if country is not None and len(country) > 0 and country != "xx":
|
|
glob.redis.zrem("ripple:leaderboard:{}:{}".format(mode, country), str(userID))
|
|
|
|
def deprecateTelegram2Fa(userID):
|
|
"""
|
|
Checks whether the user has enabled telegram 2fa on his account.
|
|
If so, disables 2fa and returns True.
|
|
If not, return False.
|
|
|
|
:param userID: id of the user
|
|
:return: True if 2fa has been disabled from the account otherwise False
|
|
"""
|
|
try:
|
|
telegram2Fa = glob.db.fetch("SELECT id FROM 2fa_telegram WHERE userid = %s LIMIT 1", (userID,))
|
|
except ProgrammingError:
|
|
# The table doesnt exist
|
|
return False
|
|
|
|
if telegram2Fa is not None:
|
|
glob.db.execute("DELETE FROM 2fa_telegram WHERE userid = %s LIMIT 1", (userID,))
|
|
return True
|
|
return False
|
|
|
|
def unlockAchievement(userID, achievementID):
|
|
glob.db.execute("INSERT INTO users_achievements (user_id, achievement_id, `time`) VALUES"
|
|
"(%s, %s, %s)", [userID, achievementID, int(time.time())])
|
|
|
|
def getAchievementsVersion(userID):
|
|
result = glob.db.fetch("SELECT achievements_version FROM users WHERE id = %s LIMIT 1", [userID])
|
|
if result is None:
|
|
return None
|
|
return result["achievements_version"]
|
|
|
|
def updateAchievementsVersion(userID):
|
|
glob.db.execute("UPDATE users SET achievements_version = %s WHERE id = %s LIMIT 1", [
|
|
glob.ACHIEVEMENTS_VERSION, userID
|
|
])
|