Add submodules
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
from objects import glob
|
||||
from helpers import logHelper as log
|
||||
from common.log import logUtils as log
|
||||
from common.ripple import userUtils
|
||||
from constants import exceptions
|
||||
from constants import serverPackets
|
||||
from objects import fokabot
|
||||
from helpers import discordBotHelper
|
||||
from helpers import userHelper
|
||||
from events import logoutEvent
|
||||
from constants import messageTemplates
|
||||
from constants import serverPackets
|
||||
from events import logoutEvent
|
||||
from objects import fokabot
|
||||
from objects import glob
|
||||
|
||||
|
||||
def joinChannel(userID = 0, channel = "", token = None, toIRC = True):
|
||||
"""
|
||||
@@ -272,7 +272,7 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
|
||||
# File and discord logs (public chat only)
|
||||
if to.startswith("#"):
|
||||
log.chat("{fro} @ {to}: {message}".format(fro=username, to=to, message=str(message.encode("utf-8"))))
|
||||
discordBotHelper.sendChatlog("**{fro} @ {to}:** {message}".format(fro=username, to=to, message=str(message.encode("utf-8"))[2:-1]))
|
||||
glob.schiavo.sendChatlog("**{fro} @ {to}:** {message}".format(fro=username, to=to, message=str(message.encode("utf-8"))[2:-1]))
|
||||
return 0
|
||||
except exceptions.userSilencedException:
|
||||
token.enqueue(serverPackets.silenceEndTime(token.getSilenceSecondsLeft()))
|
||||
@@ -314,7 +314,7 @@ def fixUsernameForIRC(username):
|
||||
return username.replace(" ", "_")
|
||||
|
||||
def IRCConnect(username):
|
||||
userID = userHelper.getID(username)
|
||||
userID = userUtils.getID(username)
|
||||
if not userID:
|
||||
log.warning("{} doesn't exist".format(username))
|
||||
return
|
||||
@@ -332,7 +332,7 @@ def IRCDisconnect(username):
|
||||
log.info("{} disconnected from IRC".format(username))
|
||||
|
||||
def IRCJoinChannel(username, channel):
|
||||
userID = userHelper.getID(username)
|
||||
userID = userUtils.getID(username)
|
||||
if not userID:
|
||||
log.warning("{} doesn't exist".format(username))
|
||||
return
|
||||
@@ -342,7 +342,7 @@ def IRCJoinChannel(username, channel):
|
||||
return joinChannel(userID, channel)
|
||||
|
||||
def IRCPartChannel(username, channel):
|
||||
userID = userHelper.getID(username)
|
||||
userID = userUtils.getID(username)
|
||||
if not userID:
|
||||
log.warning("{} doesn't exist".format(username))
|
||||
return
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from constants import bcolors
|
||||
from common.constants import bcolors
|
||||
from objects import glob
|
||||
|
||||
def printServerStartHeader(asciiArt):
|
||||
@@ -28,7 +28,7 @@ def printServerStartHeader(asciiArt):
|
||||
printColored("> Welcome to pep.py osu!bancho server v{}".format(glob.VERSION), bcolors.GREEN)
|
||||
printColored("> Made by the Ripple team", bcolors.GREEN)
|
||||
printColored("> {}https://git.zxq.co/ripple/pep.py".format(bcolors.UNDERLINE), bcolors.GREEN)
|
||||
printColored("> Press CTRL+C to exit\n",bcolors.GREEN)
|
||||
printColored("> Press CTRL+C to exit\n", bcolors.GREEN)
|
||||
|
||||
def printNoNl(string):
|
||||
"""
|
||||
|
@@ -1,222 +0,0 @@
|
||||
import queue
|
||||
import MySQLdb
|
||||
from helpers import logHelper as log
|
||||
|
||||
class worker():
|
||||
"""
|
||||
A single MySQL worker
|
||||
"""
|
||||
def __init__(self, connection, temporary=False):
|
||||
"""
|
||||
Initialize a MySQL worker
|
||||
|
||||
:param connection: database connection object
|
||||
:param temporary: if True, this worker will be flagged as temporary
|
||||
"""
|
||||
self.connection = connection
|
||||
self.temporary = temporary
|
||||
log.debug("Created MySQL worker. Temporary: {}".format(self.temporary))
|
||||
|
||||
def ping(self):
|
||||
"""
|
||||
Ping MySQL server using this worker.
|
||||
|
||||
:return: True if connected, False if error occured.
|
||||
"""
|
||||
try:
|
||||
self.connection.cursor(MySQLdb.cursors.DictCursor).execute("SELECT 1+1")
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def __del__(self):
|
||||
"""
|
||||
Close connection to the server
|
||||
|
||||
:return:
|
||||
"""
|
||||
self.connection.close()
|
||||
log.debug("Destroyed MySQL worker.")
|
||||
|
||||
class connectionsPool():
|
||||
"""
|
||||
A MySQL workers pool
|
||||
"""
|
||||
def __init__(self, host, username, password, database, initialSize=16):
|
||||
"""
|
||||
Initialize a MySQL connections pool
|
||||
|
||||
:param host: MySQL host
|
||||
:param username: MySQL username
|
||||
:param password: MySQL password
|
||||
:param database: MySQL database name
|
||||
:param initialSize: initial pool size
|
||||
"""
|
||||
self.config = (host, username, password, database)
|
||||
self.maxSize = initialSize
|
||||
self.pool = queue.Queue(0)
|
||||
self.consecutiveEmptyPool = 0
|
||||
self.fillPool()
|
||||
|
||||
def newWorker(self, temporary=False):
|
||||
"""
|
||||
Create a new worker.
|
||||
|
||||
:param temporary: if True, flag the worker as temporary
|
||||
:return: instance of worker class
|
||||
"""
|
||||
db = MySQLdb.connect(*self.config)
|
||||
db.autocommit(True)
|
||||
conn = worker(db, temporary)
|
||||
return conn
|
||||
|
||||
def expandPool(self, newWorkers=5):
|
||||
"""
|
||||
Add some new workers to the pool
|
||||
|
||||
:param newWorkers: number of new workers
|
||||
:return:
|
||||
"""
|
||||
self.maxSize += newWorkers
|
||||
self.fillPool()
|
||||
|
||||
def fillPool(self):
|
||||
"""
|
||||
Fill the queue with workers until its maxSize
|
||||
|
||||
:return:
|
||||
"""
|
||||
size = self.pool.qsize()
|
||||
if self.maxSize > 0 and size >= self.maxSize:
|
||||
return
|
||||
newConnections = self.maxSize-size
|
||||
for _ in range(0, newConnections):
|
||||
self.pool.put_nowait(self.newWorker())
|
||||
|
||||
def getWorker(self):
|
||||
"""
|
||||
Get a MySQL connection worker from the pool.
|
||||
If the pool is empty, a new temporary worker is created.
|
||||
|
||||
:return: instance of worker class
|
||||
"""
|
||||
|
||||
if self.pool.empty():
|
||||
# The pool is empty. Spawn a new temporary worker
|
||||
log.warning("Using temporary worker")
|
||||
worker = self.newWorker(True)
|
||||
|
||||
# Increment saturation
|
||||
self.consecutiveEmptyPool += 1
|
||||
|
||||
# If the pool is usually empty, expand it
|
||||
if self.consecutiveEmptyPool >= 5:
|
||||
log.warning("MySQL connections pool is saturated. Filling connections pool.")
|
||||
self.expandPool()
|
||||
else:
|
||||
# The pool is not empty. Get worker from the pool
|
||||
# and reset saturation counter
|
||||
worker = self.pool.get()
|
||||
self.consecutiveEmptyPool = 0
|
||||
|
||||
# Return the connection
|
||||
return worker
|
||||
|
||||
def putWorker(self, worker):
|
||||
"""
|
||||
Put the worker back in the pool.
|
||||
If the worker is temporary, close the connection
|
||||
and destroy the object
|
||||
|
||||
:param worker: worker object
|
||||
:return:
|
||||
"""
|
||||
if worker.temporary:
|
||||
del worker
|
||||
else:
|
||||
self.pool.put_nowait(worker)
|
||||
|
||||
class db:
|
||||
"""
|
||||
A MySQL helper with multiple workers
|
||||
"""
|
||||
def __init__(self, host, username, password, database, initialSize):
|
||||
"""
|
||||
Initialize a new MySQL database helper with multiple workers.
|
||||
This class is thread safe.
|
||||
|
||||
:param host: MySQL host
|
||||
:param username: MySQL username
|
||||
:param password: MySQL password
|
||||
:param database: MySQL database name
|
||||
:param initialSize: initial pool size
|
||||
"""
|
||||
self.pool = connectionsPool(host, username, password, database, initialSize)
|
||||
|
||||
def execute(self, query, params = ()):
|
||||
"""
|
||||
Executes a query
|
||||
|
||||
:param query: query to execute. You can bind parameters with %s
|
||||
:param params: parameters list. First element replaces first %s and so on
|
||||
"""
|
||||
cursor = None
|
||||
worker = self.pool.getWorker()
|
||||
|
||||
try:
|
||||
# Create cursor, execute query and commit
|
||||
cursor = worker.connection.cursor(MySQLdb.cursors.DictCursor)
|
||||
cursor.execute(query, params)
|
||||
log.debug(query)
|
||||
return cursor.lastrowid
|
||||
except MySQLdb.OperationalError:
|
||||
del worker
|
||||
worker = None
|
||||
return self.execute(query, params)
|
||||
finally:
|
||||
# Close the cursor and release worker's lock
|
||||
if cursor is not None:
|
||||
cursor.close()
|
||||
if worker is not None:
|
||||
self.pool.putWorker(worker)
|
||||
|
||||
def fetch(self, query, params = (), all = False):
|
||||
"""
|
||||
Fetch a single value from db that matches given query
|
||||
|
||||
:param query: query to execute. You can bind parameters with %s
|
||||
:param params: parameters list. First element replaces first %s and so on
|
||||
:param all: fetch one or all values. Used internally. Use fetchAll if you want to fetch all values
|
||||
"""
|
||||
cursor = None
|
||||
worker = self.pool.getWorker()
|
||||
|
||||
try:
|
||||
# Create cursor, execute the query and fetch one/all result(s)
|
||||
cursor = worker.connection.cursor(MySQLdb.cursors.DictCursor)
|
||||
cursor.execute(query, params)
|
||||
log.debug(query)
|
||||
if all == True:
|
||||
return cursor.fetchall()
|
||||
else:
|
||||
return cursor.fetchone()
|
||||
except MySQLdb.OperationalError:
|
||||
del worker
|
||||
worker = None
|
||||
return self.fetch(query, params, all)
|
||||
finally:
|
||||
# Close the cursor and release worker's lock
|
||||
if cursor is not None:
|
||||
cursor.close()
|
||||
if worker is not None:
|
||||
self.pool.putWorker(worker)
|
||||
|
||||
def fetchAll(self, query, params = ()):
|
||||
"""
|
||||
Fetch all values from db that matche given query.
|
||||
Calls self.fetch with all = True.
|
||||
|
||||
:param query: query to execute. You can bind parameters with %s
|
||||
:param params: parameters list. First element replaces first %s and so on
|
||||
"""
|
||||
return self.fetch(query, params, True)
|
@@ -1,136 +0,0 @@
|
||||
from constants import mods
|
||||
from time import gmtime, strftime
|
||||
import hashlib
|
||||
|
||||
def stringMd5(string):
|
||||
"""
|
||||
Return string's md5
|
||||
|
||||
string -- string to hash
|
||||
return -- string's md5 hash
|
||||
"""
|
||||
d = hashlib.md5()
|
||||
d.update(string.encode("utf-8"))
|
||||
return d.hexdigest()
|
||||
|
||||
def stringToBool(s):
|
||||
"""
|
||||
Convert a string (True/true/1) to bool
|
||||
|
||||
s -- string/int value
|
||||
return -- True/False
|
||||
"""
|
||||
return s == "True" or s == "true" or s == "1" or s == 1
|
||||
|
||||
def hexString(s):
|
||||
"""
|
||||
Output s' bytes in HEX
|
||||
|
||||
s -- string
|
||||
return -- string with hex value
|
||||
"""
|
||||
return ":".join("{:02x}".format(ord(str(c))) for c in s)
|
||||
|
||||
def readableMods(__mods):
|
||||
"""
|
||||
Return a string with readable std mods.
|
||||
Used to convert a mods number for oppai
|
||||
|
||||
__mods -- mods bitwise number
|
||||
return -- readable mods string, eg HDDT
|
||||
"""
|
||||
r = ""
|
||||
if __mods == 0:
|
||||
return r
|
||||
if __mods & mods.NoFail > 0:
|
||||
r += "NF"
|
||||
if __mods & mods.Easy > 0:
|
||||
r += "EZ"
|
||||
if __mods & mods.Hidden > 0:
|
||||
r += "HD"
|
||||
if __mods & mods.HardRock > 0:
|
||||
r += "HR"
|
||||
if __mods & mods.DoubleTime > 0:
|
||||
r += "DT"
|
||||
if __mods & mods.HalfTime > 0:
|
||||
r += "HT"
|
||||
if __mods & mods.Flashlight > 0:
|
||||
r += "FL"
|
||||
if __mods & mods.SpunOut > 0:
|
||||
r += "SO"
|
||||
|
||||
return r
|
||||
|
||||
def getRank(gameMode, __mods, acc, c300, c100, c50, cmiss):
|
||||
"""
|
||||
Return a string with rank/grade for a given score.
|
||||
Used mainly for "tillerino"
|
||||
|
||||
gameMode -- mode (0 = osu!, 1 = Taiko, 2 = CtB, 3 = osu!mania)
|
||||
__mods -- mods bitwise number
|
||||
acc -- accuracy
|
||||
c300 -- 300 hit count
|
||||
c100 -- 100 hit count
|
||||
c50 -- 50 hit count
|
||||
cmiss -- miss count
|
||||
return -- rank/grade string
|
||||
"""
|
||||
total = c300 + c100 + c50 + cmiss
|
||||
hdfl = (__mods & (mods.Hidden | mods.Flashlight | mods.FadeIn)) > 0
|
||||
|
||||
ss = "sshd" if hdfl else "ss"
|
||||
s = "shd" if hdfl else "s"
|
||||
|
||||
if gameMode == 0 or gameMode == 1:
|
||||
# osu!std / taiko
|
||||
ratio300 = c300 / total
|
||||
ratio50 = c50 / total
|
||||
if ratio300 == 1:
|
||||
return ss
|
||||
if ratio300 > 0.9 and ratio50 <= 0.01 and cmiss == 0:
|
||||
return s
|
||||
if (ratio300 > 0.8 and cmiss == 0) or (ratio300 > 0.9):
|
||||
return "a"
|
||||
if (ratio300 > 0.7 and cmiss == 0) or (ratio300 > 0.8):
|
||||
return "b"
|
||||
if ratio300 > 0.6:
|
||||
return "c"
|
||||
return "d"
|
||||
elif gameMode == 2:
|
||||
# CtB
|
||||
if acc == 100:
|
||||
return ss
|
||||
if acc > 98:
|
||||
return s
|
||||
if acc > 94:
|
||||
return "a"
|
||||
if acc > 90:
|
||||
return "b"
|
||||
if acc > 85:
|
||||
return "c"
|
||||
return "d"
|
||||
elif gameMode == 3:
|
||||
# osu!mania
|
||||
if acc == 100:
|
||||
return ss
|
||||
if acc > 95:
|
||||
return s
|
||||
if acc > 90:
|
||||
return "a"
|
||||
if acc > 80:
|
||||
return "b"
|
||||
if acc > 70:
|
||||
return "c"
|
||||
return "d"
|
||||
|
||||
return "a"
|
||||
|
||||
def strContains(s, w):
|
||||
return (' ' + w + ' ') in (' ' + s + ' ')
|
||||
|
||||
def getTimestamp():
|
||||
"""
|
||||
Return current time in YYYY-MM-DD HH:MM:SS format.
|
||||
Used in logs.
|
||||
"""
|
||||
return strftime("%Y-%m-%d %H:%M:%S", gmtime())
|
@@ -1,8 +1,9 @@
|
||||
import urllib.request
|
||||
import json
|
||||
import urllib.request
|
||||
|
||||
from common.log import logUtils as log
|
||||
from objects import glob
|
||||
|
||||
from helpers import logHelper as log
|
||||
|
||||
def getCountry(ip):
|
||||
"""
|
||||
|
@@ -1,135 +0,0 @@
|
||||
from constants import bcolors
|
||||
from helpers import discordBotHelper
|
||||
from helpers import generalFunctions
|
||||
from objects import glob
|
||||
from helpers import userHelper
|
||||
import time
|
||||
import os
|
||||
ENDL = "\n" if os.name == "posix" else "\r\n"
|
||||
|
||||
def logMessage(message, alertType = "INFO", messageColor = bcolors.ENDC, discord = None, alertDev = False, of = None, stdout = True):
|
||||
"""
|
||||
Logs a message to stdout/discord/file
|
||||
|
||||
message -- message to log
|
||||
alertType -- can be any string. Standard types: INFO, WARNING and ERRORS. Defalt: INFO
|
||||
messageColor -- message color (see constants.bcolors). Default = bcolots.ENDC (no color)
|
||||
discord -- discord channel (bunker/cm/staff/general). Optional. Default = None
|
||||
alertDev -- if True, devs will receive an hl on discord. Default: False
|
||||
of -- if not None but a string, log the message to that file (inside .data folder). Eg: "warnings.txt" Default: None (don't log to file)
|
||||
stdout -- if True, print the message to stdout. Default: True
|
||||
"""
|
||||
# Get type color from alertType
|
||||
if alertType == "INFO":
|
||||
typeColor = bcolors.GREEN
|
||||
elif alertType == "WARNING":
|
||||
typeColor = bcolors.YELLOW
|
||||
elif alertType == "ERROR":
|
||||
typeColor = bcolors.RED
|
||||
elif alertType == "CHAT":
|
||||
typeColor = bcolors.BLUE
|
||||
elif alertType == "DEBUG":
|
||||
typeColor = bcolors.PINK
|
||||
else:
|
||||
typeColor = bcolors.ENDC
|
||||
|
||||
# Message without colors
|
||||
finalMessage = "[{time}] {type} - {message}".format(time=generalFunctions.getTimestamp(), type=alertType, message=message)
|
||||
|
||||
# Message with colors
|
||||
finalMessageConsole = "{typeColor}[{time}] {type}{endc} - {messageColor}{message}{endc}".format(
|
||||
time=generalFunctions.getTimestamp(),
|
||||
type=alertType,
|
||||
message=message,
|
||||
|
||||
typeColor=typeColor,
|
||||
messageColor=messageColor,
|
||||
endc=bcolors.ENDC)
|
||||
|
||||
# Log to console
|
||||
if stdout:
|
||||
print(finalMessageConsole)
|
||||
|
||||
# Log to discord if needed
|
||||
if discord is not None:
|
||||
if discord == "bunker":
|
||||
discordBotHelper.sendConfidential(message, alertDev)
|
||||
elif discord == "cm":
|
||||
discordBotHelper.sendCM(message)
|
||||
elif discord == "staff":
|
||||
discordBotHelper.sendStaff(message)
|
||||
elif discord == "general":
|
||||
discordBotHelper.sendGeneral(message)
|
||||
|
||||
# Log to file if needed
|
||||
if of is not None:
|
||||
glob.fileBuffers.write(".data/"+of, finalMessage+ENDL)
|
||||
|
||||
def warning(message, discord = None, alertDev = False):
|
||||
"""
|
||||
Log a warning to stdout (always) and discord (optional)
|
||||
|
||||
message -- warning message
|
||||
discord -- if not None, send message to that discord channel through schiavo. Optional. Default = None
|
||||
alertDev -- if True, send al hl to devs on discord. Optional. Default = False.
|
||||
"""
|
||||
logMessage(message, "WARNING", bcolors.YELLOW, discord, alertDev)
|
||||
|
||||
def error(message, discord = None, alertDev = True):
|
||||
"""
|
||||
Log an error to stdout (always) and discord (optional)
|
||||
|
||||
message -- error message
|
||||
discord -- if not None, send message to that discord channel through schiavo. Optional. Default = None
|
||||
alertDev -- if True, send al hl to devs on discord. Optional. Default = False.
|
||||
"""
|
||||
logMessage(message, "ERROR", bcolors.RED, discord, alertDev)
|
||||
|
||||
def info(message, discord = None, alertDev = False):
|
||||
"""
|
||||
Log an info message to stdout
|
||||
|
||||
message -- info message
|
||||
discord -- if not None, send message to that discord channel through schiavo. Optional. Default = None
|
||||
alertDev -- if True, send al hl to devs on discord. Optional. Default = False.
|
||||
"""
|
||||
logMessage(message, "INFO", bcolors.ENDC, discord, alertDev)
|
||||
|
||||
def debug(message):
|
||||
"""
|
||||
Log a debug message to stdout if server is running in debug mode
|
||||
|
||||
message -- debug message
|
||||
"""
|
||||
if glob.debug:
|
||||
logMessage(message, "DEBUG", bcolors.PINK)
|
||||
|
||||
def chat(message):
|
||||
"""
|
||||
Log public messages to stdout and chatlog_public.txt
|
||||
|
||||
message -- chat message
|
||||
"""
|
||||
logMessage(message, "CHAT", bcolors.BLUE, of="chatlog_public.txt")
|
||||
|
||||
def pm(message):
|
||||
"""
|
||||
Log private messages to stdout and chatlog_private.txt
|
||||
|
||||
message -- chat message
|
||||
"""
|
||||
logMessage(message, "CHAT", bcolors.BLUE, of="chatlog_private.txt")
|
||||
|
||||
def rap(userID, message, discord=False, through="FokaBot"):
|
||||
"""
|
||||
Log a private message to Admin logs
|
||||
|
||||
userID -- userID of who made the action
|
||||
message -- message without subject (eg: "is a meme" becomes "user is a meme")
|
||||
discord -- if True, send message to discord
|
||||
through -- "through" thing string. Optional. Default: "FokaBot"
|
||||
"""
|
||||
glob.db.execute("INSERT INTO rap_logs (id, userid, text, datetime, through) VALUES (NULL, %s, %s, %s, %s)", [userID, message, int(time.time()), through])
|
||||
if discord:
|
||||
username = userHelper.getUsername(userID)
|
||||
logMessage("{} {}".format(username, message), discord=True)
|
@@ -1,35 +0,0 @@
|
||||
from helpers import cryptHelper
|
||||
import base64
|
||||
import bcrypt
|
||||
|
||||
def checkOldPassword(password, salt, rightPassword):
|
||||
"""
|
||||
Check if password+salt corresponds to rightPassword
|
||||
|
||||
password -- input password
|
||||
salt -- password's salt
|
||||
rightPassword -- right password
|
||||
return -- bool
|
||||
"""
|
||||
return rightPassword == cryptHelper.crypt(password, "$2y$" + str(base64.b64decode(salt)))
|
||||
|
||||
def checkNewPassword(password, dbPassword):
|
||||
"""
|
||||
Check if a password (version 2) is right.
|
||||
|
||||
password -- input password
|
||||
dbPassword -- the password in the database
|
||||
return -- bool
|
||||
"""
|
||||
password = password.encode("utf8")
|
||||
dbPassword = dbPassword.encode("utf8")
|
||||
return bcrypt.hashpw(password, dbPassword) == dbPassword
|
||||
|
||||
def genBcrypt(password):
|
||||
"""
|
||||
Bcrypts a password.
|
||||
|
||||
password -- the password to hash.
|
||||
return -- bytestring
|
||||
"""
|
||||
return bcrypt.hashpw(password.encode("utf8"), bcrypt.gensalt(10, b'2a'))
|
@@ -1,101 +0,0 @@
|
||||
import tornado
|
||||
import tornado.web
|
||||
import tornado.gen
|
||||
from tornado.ioloop import IOLoop
|
||||
from objects import glob
|
||||
import threading
|
||||
from helpers import logHelper as log
|
||||
|
||||
class asyncRequestHandler(tornado.web.RequestHandler):
|
||||
"""
|
||||
Tornado asynchronous request handler
|
||||
create a class that extends this one (requestHelper.asyncRequestHandler)
|
||||
use asyncGet() and asyncPost() instad of get() and post().
|
||||
Done. I'm not kidding.
|
||||
"""
|
||||
@tornado.web.asynchronous
|
||||
@tornado.gen.engine
|
||||
def get(self, *args, **kwargs):
|
||||
try:
|
||||
yield tornado.gen.Task(runBackground, (self.asyncGet, tuple(args), dict(kwargs)))
|
||||
except Exception as e:
|
||||
yield tornado.gen.Task(self.captureException, exc_info=True)
|
||||
finally:
|
||||
if not self._finished:
|
||||
self.finish()
|
||||
|
||||
@tornado.web.asynchronous
|
||||
@tornado.gen.engine
|
||||
def post(self, *args, **kwargs):
|
||||
try:
|
||||
yield tornado.gen.Task(runBackground, (self.asyncPost, tuple(args), dict(kwargs)))
|
||||
except Exception as e:
|
||||
yield tornado.gen.Task(self.captureException, exc_info=True)
|
||||
finally:
|
||||
if not self._finished:
|
||||
self.finish()
|
||||
|
||||
def asyncGet(self, *args, **kwargs):
|
||||
self.send_error(405)
|
||||
self.finish()
|
||||
|
||||
def asyncPost(self, *args, **kwargs):
|
||||
self.send_error(405)
|
||||
self.finish()
|
||||
|
||||
def getRequestIP(self):
|
||||
realIP = self.request.headers.get("X-Forwarded-For") if glob.cloudflare == True else self.request.headers.get("X-Real-IP")
|
||||
if realIP is not None:
|
||||
return realIP
|
||||
return self.request.remote_ip
|
||||
|
||||
def runBackground(data, callback):
|
||||
"""
|
||||
Run a function in the background.
|
||||
Used to handle multiple requests at the same time
|
||||
"""
|
||||
func, args, kwargs = data
|
||||
def _callback(result):
|
||||
#glob.busyThreads -= 1
|
||||
IOLoop.instance().add_callback(lambda: callback(result))
|
||||
glob.pool.apply_async(func, args, kwargs, _callback)
|
||||
#threading.Thread(target=checkPoolSaturation).start()
|
||||
#glob.busyThreads += 1
|
||||
|
||||
def checkPoolSaturation():
|
||||
"""
|
||||
Check the number of busy threads in connections pool.
|
||||
If the pool is 100% busy, log a message to sentry
|
||||
"""
|
||||
size = int(glob.conf.config["server"]["threads"])
|
||||
if glob.busyThreads >= size:
|
||||
msg = "Connections threads pool is saturated!"
|
||||
log.warning(msg)
|
||||
glob.application.sentry_client.captureMessage(msg, level="warning", extra={
|
||||
"workersBusy": glob.busyThreads,
|
||||
"workersTotal": size
|
||||
})
|
||||
|
||||
def checkArguments(arguments, requiredArguments):
|
||||
"""
|
||||
Check that every requiredArguments elements are in arguments
|
||||
|
||||
arguments -- full argument list, from tornado
|
||||
requiredArguments -- required arguments list es: ["u", "ha"]
|
||||
handler -- handler string name to print in exception. Optional
|
||||
return -- True if all arguments are passed, none if not
|
||||
"""
|
||||
for i in requiredArguments:
|
||||
if i not in arguments:
|
||||
return False
|
||||
return True
|
||||
|
||||
def printArguments(t):
|
||||
"""
|
||||
Print passed arguments, for debug purposes
|
||||
|
||||
t -- tornado object (self)
|
||||
"""
|
||||
print("ARGS::")
|
||||
for i in t.request.arguments:
|
||||
print ("{}={}".format(i, t.get_argument(i)))
|
@@ -1,15 +1,18 @@
|
||||
from objects import glob
|
||||
from constants import serverPackets
|
||||
from helpers import consoleHelper
|
||||
import psutil
|
||||
import math
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import threading
|
||||
import signal
|
||||
from helpers import logHelper as log
|
||||
from constants import bcolors
|
||||
import time
|
||||
import math
|
||||
|
||||
import psutil
|
||||
|
||||
from common.constants import bcolors
|
||||
from common.log import logUtils as log
|
||||
from constants import serverPackets
|
||||
from helpers import consoleHelper
|
||||
from objects import glob
|
||||
|
||||
|
||||
def dispose():
|
||||
"""
|
||||
|
@@ -1,640 +0,0 @@
|
||||
from helpers import passwordHelper
|
||||
from constants import gameModes
|
||||
from constants import privileges
|
||||
from helpers import generalFunctions
|
||||
from objects import glob
|
||||
from helpers import logHelper as log
|
||||
import time
|
||||
from constants import privileges
|
||||
|
||||
def getID(username):
|
||||
"""
|
||||
Get username's user ID from userID cache (if cache hit)
|
||||
or from db (and cache it for other requests) if cache miss
|
||||
|
||||
username -- user
|
||||
return -- user id or 0
|
||||
"""
|
||||
# Add to cache if needed
|
||||
if username not in glob.userIDCache:
|
||||
userID = glob.db.fetch("SELECT id FROM users WHERE username = %s LIMIT 1", [username])
|
||||
if userID == None:
|
||||
return 0
|
||||
glob.userIDCache[username] = userID["id"]
|
||||
|
||||
# Get userID from cache
|
||||
return glob.userIDCache[username]
|
||||
|
||||
def checkLogin(userID, password):
|
||||
"""
|
||||
Check userID's login with specified password
|
||||
|
||||
db -- database connection
|
||||
userID -- user id
|
||||
password -- plain md5 password
|
||||
return -- True or False
|
||||
"""
|
||||
# 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 passwordHelper.checkNewPassword(password, passwordData["password_md5"])
|
||||
if passwordData["password_version"] == 1:
|
||||
ok = passwordHelper.checkOldPassword(password, passwordData["salt"], passwordData["password_md5"])
|
||||
if not ok: return False
|
||||
newpass = passwordHelper.genBcrypt(password)
|
||||
glob.db.execute("UPDATE users SET password_md5=%s, salt='', password_version='2' WHERE id = %s LIMIT 1", [newpass, userID])
|
||||
|
||||
def exists(userID):
|
||||
"""
|
||||
Check if userID exists
|
||||
|
||||
userID -- user ID to check
|
||||
return -- bool
|
||||
"""
|
||||
result = glob.db.fetch("SELECT id FROM users WHERE id = %s LIMIT 1", [userID])
|
||||
if result is None:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def getSilenceEnd(userID):
|
||||
"""
|
||||
Get userID's **ABSOLUTE** silence end UNIX time
|
||||
Remember to subtract time.time() to get the actual silence time
|
||||
|
||||
userID -- userID
|
||||
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
|
||||
|
||||
userID -- userID
|
||||
seconds -- silence length in seconds
|
||||
silenceReason -- Silence reason shown on website
|
||||
author -- userID of who silenced the user. Default: 999
|
||||
"""
|
||||
# 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])
|
||||
|
||||
# Loh
|
||||
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 getRankedScore(userID, gameMode):
|
||||
"""
|
||||
Get userID's ranked score relative to gameMode
|
||||
|
||||
userID -- userID
|
||||
gameMode -- int value, see gameModes
|
||||
return -- ranked score
|
||||
"""
|
||||
modeForDB = gameModes.getGameModeForDB(gameMode)
|
||||
return glob.db.fetch("SELECT ranked_score_"+modeForDB+" FROM users_stats WHERE id = %s LIMIT 1", [userID])["ranked_score_"+modeForDB]
|
||||
|
||||
|
||||
def getTotalScore(userID, gameMode):
|
||||
"""
|
||||
Get userID's total score relative to gameMode
|
||||
|
||||
userID -- userID
|
||||
gameMode -- int value, see gameModes
|
||||
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
|
||||
|
||||
userID -- userID
|
||||
gameMode -- int value, see gameModes
|
||||
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
|
||||
|
||||
userID -- userID
|
||||
gameMode -- int value, see gameModes
|
||||
return -- game rank
|
||||
"""
|
||||
|
||||
modeForDB = gameModes.getGameModeForDB(gameMode)
|
||||
result = glob.db.fetch("SELECT position FROM leaderboard_"+modeForDB+" WHERE user = %s LIMIT 1", [userID])
|
||||
if result is None:
|
||||
return 0
|
||||
else:
|
||||
return result["position"]
|
||||
|
||||
def getPlaycount(userID, gameMode):
|
||||
"""
|
||||
Get userID's playcount relative to gameMode
|
||||
|
||||
userID -- userID
|
||||
gameMode -- int value, see gameModes
|
||||
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 getUsername(userID):
|
||||
"""
|
||||
Get userID's username
|
||||
|
||||
userID -- userID
|
||||
return -- username
|
||||
"""
|
||||
|
||||
return glob.db.fetch("SELECT username FROM users WHERE id = %s LIMIT 1", [userID])["username"]
|
||||
|
||||
def getFriendList(userID):
|
||||
"""
|
||||
Get userID's friendlist
|
||||
|
||||
userID -- userID
|
||||
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
|
||||
|
||||
userID -- user
|
||||
friendID -- new friend
|
||||
"""
|
||||
|
||||
# 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
|
||||
|
||||
userID -- user
|
||||
friendID -- old friend
|
||||
"""
|
||||
# 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. ¯\_(ツ)_/¯
|
||||
glob.db.execute("DELETE FROM users_relationships WHERE user1 = %s AND user2 = %s", [userID, friendID])
|
||||
|
||||
|
||||
def getCountry(userID):
|
||||
"""
|
||||
Get userID's country **(two letters)**.
|
||||
Use countryHelper.getCountryID with what that function returns
|
||||
to get osu! country ID relative to that user
|
||||
|
||||
userID -- user
|
||||
return -- country code (two letters)
|
||||
"""
|
||||
return glob.db.fetch("SELECT country FROM users_stats WHERE id = %s LIMIT 1", [userID])["country"]
|
||||
|
||||
def getPP(userID, gameMode):
|
||||
"""
|
||||
Get userID's PP relative to gameMode
|
||||
|
||||
userID -- user
|
||||
return -- PP
|
||||
"""
|
||||
|
||||
modeForDB = gameModes.getGameModeForDB(gameMode)
|
||||
return glob.db.fetch("SELECT pp_{} FROM users_stats WHERE id = %s LIMIT 1".format(modeForDB), [userID])["pp_{}".format(modeForDB)]
|
||||
|
||||
def setCountry(userID, country):
|
||||
"""
|
||||
Set userID's country (two letters)
|
||||
|
||||
userID -- userID
|
||||
country -- country letters
|
||||
"""
|
||||
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
|
||||
"""
|
||||
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 bancho_sessions table.
|
||||
Used to cache logins on LETS requests
|
||||
|
||||
userID --
|
||||
ip -- user's ip address
|
||||
"""
|
||||
glob.db.execute("INSERT INTO bancho_sessions (id, userid, ip) VALUES (NULL, %s, %s)", [userID, ip])
|
||||
|
||||
def deleteBanchoSessions(userID, ip):
|
||||
"""
|
||||
Delete this bancho session from DB
|
||||
|
||||
userID --
|
||||
ip -- user's IP address
|
||||
"""
|
||||
try:
|
||||
glob.db.execute("DELETE FROM bancho_sessions WHERE userid = %s AND ip = %s", [userID, ip])
|
||||
except:
|
||||
log.warning("Token for user: {} ip: {} doesn't exist".format(userID, ip))
|
||||
|
||||
def is2FAEnabled(userID):
|
||||
"""
|
||||
Check if 2FA is enabled on an account
|
||||
|
||||
userID --
|
||||
return -- True if 2FA is enabled, False if 2FA is disabled
|
||||
"""
|
||||
result = glob.db.fetch("SELECT id FROM 2fa_telegram WHERE userid = %s LIMIT 1", [userID])
|
||||
return True if result is not None else False
|
||||
|
||||
def check2FA(userID, ip):
|
||||
"""
|
||||
Check if an ip is trusted
|
||||
|
||||
userID --
|
||||
ip -- user's IP address
|
||||
return -- True if the IP is untrusted, False if it's trusted
|
||||
"""
|
||||
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 getUserStats(userID, gameMode):
|
||||
"""
|
||||
Get all user stats relative to gameMode with only two queries
|
||||
|
||||
userID --
|
||||
gameMode -- gameMode number
|
||||
return -- dictionary with results
|
||||
"""
|
||||
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
|
||||
result = glob.db.fetch("SELECT position FROM leaderboard_{} WHERE user = %s LIMIT 1".format(modeForDB), [userID])
|
||||
if result is None:
|
||||
stats["gameRank"] = 0
|
||||
else:
|
||||
stats["gameRank"] = result["position"]
|
||||
|
||||
# Return stats + game rank
|
||||
return stats
|
||||
|
||||
def isAllowed(userID):
|
||||
"""
|
||||
Check if userID is not banned or restricted
|
||||
|
||||
userID -- id of the user
|
||||
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
|
||||
|
||||
userID -- id of the user
|
||||
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
|
||||
|
||||
userID -- id of the user
|
||||
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
|
||||
|
||||
userID -- id of the user
|
||||
return -- True if not locked, otherwise false.
|
||||
"""
|
||||
result = glob.db.fetch("SELECT privileges FROM users WHERE id = %s LIMIT 1", [userID])
|
||||
if result != None:
|
||||
return ((result["privileges"] & privileges.USER_PUBLIC > 0) and (result["privileges"] & privileges.USER_NORMAL == 0))
|
||||
else:
|
||||
return True
|
||||
|
||||
def ban(userID):
|
||||
"""
|
||||
Ban userID
|
||||
|
||||
userID -- id of user
|
||||
"""
|
||||
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 | privileges.USER_PENDING_VERIFICATION) , banDateTime, userID])
|
||||
|
||||
def unban(userID):
|
||||
"""
|
||||
Unban userID
|
||||
|
||||
userID -- id of user
|
||||
"""
|
||||
glob.db.execute("UPDATE users SET privileges = privileges | %s, ban_datetime = 0 WHERE id = %s LIMIT 1", [ (privileges.USER_NORMAL | privileges.USER_PUBLIC) , userID])
|
||||
|
||||
def restrict(userID):
|
||||
"""
|
||||
Put userID in restricted mode
|
||||
|
||||
userID -- id of user
|
||||
"""
|
||||
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])
|
||||
|
||||
def unrestrict(userID):
|
||||
"""
|
||||
Remove restricted mode from userID.
|
||||
Same as unban().
|
||||
|
||||
userID -- id of user
|
||||
"""
|
||||
unban(userID)
|
||||
|
||||
def getPrivileges(userID):
|
||||
"""
|
||||
Return privileges for userID
|
||||
|
||||
userID -- id of user
|
||||
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 setPrivileges(userID, priv):
|
||||
"""
|
||||
Set userID's privileges in db
|
||||
|
||||
userID -- id of user
|
||||
priv -- privileges number
|
||||
"""
|
||||
glob.db.execute("UPDATE users SET privileges = %s WHERE id = %s LIMIT 1", [priv, userID])
|
||||
|
||||
def isInPrivilegeGroup(userID, groupName):
|
||||
groupPrivileges = glob.db.fetch("SELECT privileges FROM privileges_groups WHERE name = %s LIMIT 1", [groupName])
|
||||
if groupPrivileges is None:
|
||||
return False
|
||||
groupPrivileges = groupPrivileges["privileges"]
|
||||
userToken = glob.tokens.getTokenFromUserID(userID)
|
||||
if userToken is not None:
|
||||
userPrivileges = userToken.privileges
|
||||
else:
|
||||
userPrivileges = getPrivileges(userID)
|
||||
return (userPrivileges == groupPrivileges) or (userPrivileges == (groupPrivileges | privileges.USER_DONOR))
|
||||
|
||||
|
||||
def appendNotes(userID, notes, addNl = True):
|
||||
"""
|
||||
Append "notes" to current userID's "notes for CM"
|
||||
|
||||
userID -- id of user
|
||||
notes -- text to append
|
||||
addNl -- if True, prepend \n to notes. Optional. Default: True.
|
||||
"""
|
||||
if addNl:
|
||||
notes = "\n"+notes
|
||||
glob.db.execute("UPDATE users SET notes=CONCAT(COALESCE(notes, ''),%s) WHERE id = %s LIMIT 1", [notes, userID])
|
||||
|
||||
|
||||
def logHardware(userID, hashes, activation = False):
|
||||
"""
|
||||
Hardware log
|
||||
USED FOR MULTIACCOUNT DETECTION
|
||||
|
||||
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 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
|
||||
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 (IF(%(mac)s!='b4ec3c4334a0249dae95c284ec5983df', hw_user.mac = %(mac)s, 1) 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.
|
||||
|
||||
userID -- ID of the user
|
||||
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):
|
||||
# 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
|
||||
match = glob.db.fetchAll("SELECT userid FROM hw_user WHERE (IF(%(mac)s != 'b4ec3c4334a0249dae95c284ec5983df', mac = %(mac)s, 1) 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):
|
||||
"""
|
||||
userID -- id of the user
|
||||
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 cacheUserIDs():
|
||||
"""Cache userIDs in glob.userIDCache, used later with getID()."""
|
||||
data = glob.db.fetchAll("SELECT id, username FROM users WHERE privileges & {} > 0".format(privileges.USER_NORMAL))
|
||||
for i in data:
|
||||
glob.userIDCache[i["username"]] = i["id"]
|
||||
|
||||
def getDonorExpire(userID):
|
||||
"""
|
||||
Return userID's donor expiration UNIX timestamp
|
||||
:param userID:
|
||||
: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
|
Reference in New Issue
Block a user