def get(self, userID, fileMd5, country=False, friends=False, mods=-1):
		"""
		Get cached personal best rank

		:param userID: userID
		:param fileMd5: beatmap md5
		:param country: True if country leaderboard, otherwise False
		:param friends: True if friends leaderboard, otherwise False
		:param mods: leaderboard mods
		:return: 0 if cache miss, otherwise rank number
		"""
		try:
			# Make sure the value is in cache
			data = glob.redis.get("lets:personal_best_cache_relax:{}".format(userID))
			if data is None:
				raise cacheMiss()

			# Unpack cached data
			data = data.decode("utf-8").split("|")
			cachedpersonalBestRankRX = int(data[0])
			cachedfileMd5 = str(data[1])
			cachedCountry = generalUtils.stringToBool(data[2])
			cachedFriends = generalUtils.stringToBool(data[3])
			cachedMods = int(data[4])

			# Check if everything matches
			if fileMd5 != cachedfileMd5 or country != cachedCountry or friends != cachedFriends or mods != cachedMods:
				raise cacheMiss()

			# Cache hit
			log.debug("personalBestCacheRX hit")
			return cachedpersonalBestRankRX
		except cacheMiss:
			log.debug("personalBestCacheRX miss")
			return 0
Beispiel #2
0
    def loadSettings(self):
        """
		(re)load bancho_settings from DB and set values in config array
		"""
        self.config["banchoMaintenance"] = generalUtils.stringToBool(
            glob.db.fetch(
                "SELECT value_int FROM bancho_settings WHERE name = 'bancho_maintenance'"
            )["value_int"])
        self.config["freeDirect"] = generalUtils.stringToBool(
            glob.db.fetch(
                "SELECT value_int FROM bancho_settings WHERE name = 'free_direct'"
            )["value_int"])
        mainMenuIcon = glob.db.fetch(
            "SELECT file_id, url, lokasi_file FROM main_menu_icons WHERE is_current = 1 LIMIT 1"
        )
        if mainMenuIcon is None:
            self.config["menuIcon"] = ""
        else:
            imageURL = "{}/{}.png".format(mainMenuIcon["lokasi_file"],
                                          mainMenuIcon["file_id"])
            self.config["menuIcon"] = "{}|{}".format(imageURL,
                                                     mainMenuIcon["url"])
        self.config["loginNotification"] = glob.db.fetch(
            "SELECT value_string FROM bancho_settings WHERE name = 'login_notification'"
        )["value_string"]
Beispiel #3
0
	def loadSettings(self):
		"""
		(re)load bancho_settings from DB and set values in config array
		"""
		self.config["banchoMaintenance"] = generalUtils.stringToBool(glob.db.fetch("SELECT value_int FROM bancho_settings WHERE name = 'bancho_maintenance'")["value_int"])
		self.config["freeDirect"] = generalUtils.stringToBool(glob.db.fetch("SELECT value_int FROM bancho_settings WHERE name = 'free_direct'")["value_int"])
		mainMenuIcon = glob.db.fetch("SELECT file_id, url FROM main_menu_icons WHERE is_current = 1 LIMIT 1")
		if mainMenuIcon is None:
			self.config["menuIcon"] = ""
		else:
			imageURL = "https://i.ppy.sh/{}.png".format(mainMenuIcon["file_id"])
			self.config["menuIcon"] = "{}|{}".format(imageURL, mainMenuIcon["url"])
		#self.config["loginNotification"] = glob.db.fetch("SELECT value_string FROM bancho_settings WHERE name = 'login_notification'")["value_string"]
		self.config["Quotes"] = [
			"Don't forget to visit c.ussr.pl!",
			"WE SERVE THE SOVIET UNION!",
			"Ran by best VEVO channel!",
			"Stay home! Click circles!",
			"Spelchecked!",
			"Screw you segmentation!",
			"I forgot how to decrypt bcrypt",
			"Introducing  :  Time",
			"Python to the moon!",
			"We have the best devsSyntaxError: good_devs is not defined.",
			"Powered by electricity!",
			"i still can't decrypt bcrypt",
			"how to run py on 7m??",
			"It may not work, but it is fast!"
		]
Beispiel #4
0
def osuApiRequest(request, params, getFirst=True):
	"""
	Send a request to osu!api.

	request -- request type, string (es: get_beatmaps)
	params -- GET parameters, without api key or trailing ?/& (es: h=a5b99395a42bd55bc5eb1d2411cbdf8b&limit=10)
	return -- dictionary with json response if success, None if failed or empty response.
	"""
	# Make sure osuapi is enabled
	if not generalUtils.stringToBool(glob.conf.config["osuapi"]["enable"]):
		log.warning("osu!api is disabled")
		return None

	# Api request
	resp = None
	try:
		finalURL = "{}/api/{}?k={}&{}".format(glob.conf.config["osuapi"]["apiurl"], request, glob.conf.config["osuapi"]["apikey"], params)
		log.debug(finalURL)
		resp = requests.get(finalURL, timeout=5).text
		data = json.loads(resp)
		if getFirst:
			if len(data) >= 1:
				resp = data[0]
			else:
				resp = None
		else:
			resp = data
	finally:
		glob.dog.increment(glob.DATADOG_PREFIX+".osu_api.requests")
		log.debug(str(resp).encode("utf-8"))
		return resp
Beispiel #5
0
    def loadSettings(self):
        """
		(re)load bancho_settings from DB and set values in config array
		"""
        self.config["banchoMaintenance"] = generalUtils.stringToBool(
            glob.db.fetch(
                "SELECT value_int FROM bancho_settings WHERE name = 'bancho_maintenance'"
            )["value_int"])
        self.config["freeDirect"] = generalUtils.stringToBool(
            glob.db.fetch(
                "SELECT value_int FROM bancho_settings WHERE name = 'free_direct'"
            )["value_int"])
        self.config["menuIcon"] = glob.db.fetch(
            "SELECT value_string FROM bancho_settings WHERE name = 'menu_icon'"
        )["value_string"]
        self.config["loginNotification"] = glob.db.fetch(
            "SELECT value_string FROM bancho_settings WHERE name = 'login_notification'"
        )["value_string"]
Beispiel #6
0
def delta(fro, chan, message):
	if chan.startswith("#"):
		return
	if not glob.conf.config["server"]["deltaurl"].strip():
		return "Delta is disabled."
	userToken = glob.tokens.getTokenFromUserID(userUtils.getID(fro), ignoreIRC=True, _all=False)
	if userToken is None:
		return "You must be connected from a game client to switch to delta"
	if not generalUtils.stringToBool(glob.conf.config["server"]["publicdelta"]) and not userToken.admin:
		return "You can't use delta yet. Try again later."
	userToken.enqueue(serverPackets.switchServer(glob.conf.config["server"]["deltaurl"]))
	return "Connecting to delta..."
Beispiel #7
0
    def get(self,
            userID: int,
            fileMd5: str,
            country: bool = False,
            friends: bool = False,
            mods: int = -1) -> int:
        """
        Get cached personal best rank
        :param userID: userID
        :param fileMd5: beatmap md5
        :param country: True if country leaderboard, otherwise False
        :param friends: True if friends leaderboard, otherwise False
        :param mods: leaderboard mods
        :return: 0 if cache miss, otherwise rank number
        """
        try:
            # Make sure the value is in cache
            data = glob.redis.get(f"lets:personal_best_cache:{userID}")
            if data is None:
                raise cacheMiss()

            # Unpack cached data
            data = data.decode().split("|")

            # Check if everything matches
            if (fileMd5 != data[1]
                    or country != generalUtils.stringToBool(data[2])
                    or friends != generalUtils.stringToBool(data[3])
                    or mods != int(data[4])):
                raise cacheMiss()

            # Cache hit
            log.debug("personalBestCache hit")
            return int(data[0])
        except cacheMiss:
            log.debug("personalBestCache miss")
            return 0
Beispiel #8
0
    def loadSettings(self):
        """
		(re)load bancho_settings from DB and set values in config array
		"""
        self.config["banchoMaintenance"] = generalUtils.stringToBool(
            glob.db.fetch(
                "SELECT value_int FROM bancho_settings WHERE name = 'bancho_maintenance'"
            )["value_int"])
        self.config["freeDirect"] = generalUtils.stringToBool(
            glob.db.fetch(
                "SELECT value_int FROM bancho_settings WHERE name = 'free_direct'"
            )["value_int"])
        mainMenuIcon = glob.db.fetch(
            "SELECT file_id, url FROM main_menu_icons WHERE is_current = 1 LIMIT 1"
        )
        if mainMenuIcon is None:
            self.config["menuIcon"] = ""
        else:
            imageURL = "".format(mainMenuIcon["file_id"])
            self.config["menuIcon"] = "".format(imageURL, mainMenuIcon["url"])
        #self.config["loginNotification"] = glob.db.fetch("SELECT value_string FROM bancho_settings WHERE name = 'login_notification'")["value_string"]
        self.config["Quotes"] = [
            "Welcome to osuHOW!", "sakuru is a loli", "t r i a n g l e s",
            "gamer'd", "peppy did a bad job on my taxes", "osu!2013 GAMING",
            "c.eggradio.tk", "peppycode", "ripplecode", "bad python code",
            "HOWosu", "bad server", "peppy GET OUT",
            "If you don't play osu!, well, I don't even know how you got here!",
            "osu bad game", "disco prince", "habbas is funny",
            "buy the c o i n s", "no cbcc allowed, seriously, i unranked it!",
            "realistikdash why did you cheat on my old server",
            "neko cucucumber", "help my pc is on fire", "bad game",
            "i installed osu on my toaster", "peppy open up",
            "Activate Windows", "hha funi nubmer guysz psl liek an subscrbe",
            "peppy (osu osx)", "Go to Settings to activate Windows.",
            "i hate u ripple", "Second biggest exporter of triangles!",
            '"yuuor amd" -hubz'
        ]
Beispiel #9
0
def getOsuFileFromID(beatmapID):
	"""
	Send a request to osu! servers to download a .osu file from beatmap ID
	Used to get .osu files for oppai

	beatmapID -- ID of beatmap (not beatmapset) to download
	return -- .osu file content if success, None if failed
	"""
	# Make sure osuapi is enabled
	if not generalUtils.stringToBool(glob.conf.config["osuapi"]["enable"]):
		log.warning("osuapi is disabled")
		return None
	response = None
	try:
		URL = "{}/osu/{}".format(glob.conf.config["osuapi"]["apiurl"], beatmapID)
		response = requests.get(URL, timeout=20).content
	finally:
		glob.dog.increment(glob.DATADOG_PREFIX+".osu_api.osu_file_requests")
		return response
Beispiel #10
0
def getOsuFileFromName(fileName):
	"""
	Send a request to osu! servers to download a .osu file from file name
	Used to update beatmaps

	fileName -- .osu file name to download
	return -- .osu file content if success, None if failed
	"""
	# Make sure osuapi is enabled
	if not generalUtils.stringToBool(glob.conf.config["osuapi"]["enable"]):
		log.warning("osuapi is disabled")
		return None
	response = None
	try:
		URL = "{}/web/maps/{}".format(glob.conf.config["osuapi"]["apiurl"], quote(fileName))
		req = requests.get(URL, timeout=20)
		req.encoding = "utf-8"
		response = req.content
	finally:
		glob.dog.increment(glob.DATADOG_PREFIX+".osu_api.osu_file_requests")
		return response
Beispiel #11
0
        glob.tokens.usersTimeoutCheckLoop()
        consoleHelper.printDone()

        # Initialize spam protection reset loop
        consoleHelper.printNoNl(
            "> Initializing spam protection reset loop... ")
        glob.tokens.spamProtectionResetLoop()
        consoleHelper.printDone()

        # Initialize multiplayer cleanup loop
        consoleHelper.printNoNl("> Initializing multiplayer cleanup loop... ")
        glob.matches.cleanupLoop()
        consoleHelper.printDone()

        # Localize warning
        glob.localize = generalUtils.stringToBool(
            glob.conf.config["localize"]["enable"])
        if not glob.localize:
            consoleHelper.printColored(
                "[!] Warning! Users localization is disabled!", bcolors.YELLOW)

        # Discord
        if generalUtils.stringToBool(glob.conf.config["discord"]["enable"]):
            glob.schiavo = schiavo.schiavo(
                glob.conf.config["discord"]["boturl"], "**pep.py**")
        else:
            consoleHelper.printColored(
                "[!] Warning! Discord logging is disabled!", bcolors.YELLOW)

        # Gzip
        glob.gzip = generalUtils.stringToBool(
            glob.conf.config["server"]["gzip"])
Beispiel #12
0
        # Save lets version in redis
        glob.redis.set("lets:version", glob.VERSION)

        # Create threads pool
        try:
            consoleHelper.printNoNl("> Creating threads pool... ")
            glob.pool = ThreadPool(int(glob.conf.config["server"]["threads"]))
            consoleHelper.printDone()
        except:
            consoleHelper.printError()
            consoleHelper.printColored(
                "[!] Error while creating threads pool. Please check your config.ini and run the server again",
                bcolors.RED)

        # Check osuapi
        if not generalUtils.stringToBool(glob.conf.config["osuapi"]["enable"]):
            consoleHelper.printColored(
                "[!] osu!api features are disabled. If you don't have a valid beatmaps table, all beatmaps will show as unranked",
                bcolors.YELLOW)
            if int(glob.conf.config["server"]["beatmapcacheexpire"]) > 0:
                consoleHelper.printColored(
                    "[!] IMPORTANT! Your beatmapcacheexpire in config.ini is > 0 and osu!api features are disabled.\nWe do not reccoment this, because too old beatmaps will be shown as unranked.\nSet beatmapcacheexpire to 0 to disable beatmap latest update check and fix that issue.",
                    bcolors.YELLOW)

        # Load achievements
        consoleHelper.printNoNl("Loading achievements... ")
        try:
            secret.achievements.utils.load_achievements()
        except Exception as e:
            consoleHelper.printError()
            consoleHelper.printColored(
Beispiel #13
0
                        help="force number of workers",
                        required=False)
    parser.add_argument('-v',
                        '--verbose',
                        help="run ripp in verbose/debug mode",
                        required=False,
                        action='store_true')
    args = parser.parse_args()

    # Platform
    print("Running under {}".format("UNIX" if UNIX == True else "WIN32"))

    # Load config
    consoleHelper.printNoNl("> Reading config file... ")
    glob.conf = config.config("config.ini")
    glob.debug = generalUtils.stringToBool(glob.conf.config["server"]["debug"])
    consoleHelper.printDone()

    # Get workers from arguments if set
    workers = 0
    if args.workers is not None:
        workers = int(args.workers)

    # Connect to MySQL
    try:
        consoleHelper.printNoNl("> Connecting to MySQL db")
        glob.db = dbConnector.db(glob.conf.config["db"]["host"],
                                 glob.conf.config["db"]["username"],
                                 glob.conf.config["db"]["password"],
                                 glob.conf.config["db"]["database"],
                                 max(workers, MAX_WORKERS))
Beispiel #14
0
def main() -> int:
    # AGPL license agreement
    try:
        agpl.check_license("ripple", "LETS")
    except agpl.LicenseError as e:
        print(str(e))
        return 1

    try:
        consoleHelper.printServerStartHeader(True)

        # Read config
        consoleHelper.printNoNl("> Reading config file... ")
        glob.conf = config.config("config.ini")

        if glob.conf.default:
            # We have generated a default config.ini, quit server
            consoleHelper.printWarning()
            consoleHelper.printColored(
                "[!] config.ini not found. A default one has been generated.",
                bcolors.YELLOW)
            consoleHelper.printColored(
                "[!] Please edit your config.ini and run the server again.",
                bcolors.YELLOW)
            return 1

        # If we haven't generated a default config.ini, check if it's valid
        if not glob.conf.checkConfig():
            consoleHelper.printError()
            consoleHelper.printColored(
                "[!] Invalid config.ini. Please configure it properly",
                bcolors.RED)
            consoleHelper.printColored(
                "[!] Delete your config.ini to generate a default one",
                bcolors.RED)
            return 1
        else:
            consoleHelper.printDone()

        # Read additional config file
        consoleHelper.printNoNl("> Loading additional config file... ")
        try:
            if not os.path.isfile(glob.conf.config["custom"]["config"]):
                consoleHelper.printWarning()
                consoleHelper.printColored(
                    "[!] Missing config file at {}; A default one has been generated at this location."
                    .format(glob.conf.config["custom"]["config"]),
                    bcolors.YELLOW)
                shutil.copy("common/default_config.json",
                            glob.conf.config["custom"]["config"])

            with open(glob.conf.config["custom"]["config"], "r") as f:
                glob.conf.extra = json.load(f)

            consoleHelper.printDone()
        except:
            consoleHelper.printWarning()
            consoleHelper.printColored(
                "[!] Unable to load custom config at {}".format(
                    glob.conf.config["custom"]["config"]), bcolors.RED)
            return 1

        # Create data/oppai maps folder if needed
        consoleHelper.printNoNl("> Checking folders... ")
        paths = [
            ".data", ".data/oppai", ".data/catch_the_pp",
            glob.conf.config["server"]["replayspath"],
            "{}_relax".format(glob.conf.config["server"]["replayspath"]),
            glob.conf.config["server"]["beatmapspath"],
            glob.conf.config["server"]["screenshotspath"]
        ]
        for i in paths:
            if not os.path.exists(i):
                os.makedirs(i, 0o770)
        consoleHelper.printDone()

        # Connect to db
        try:
            consoleHelper.printNoNl("> Connecting to MySQL database... ")
            glob.db = dbConnector.db(glob.conf.config["db"]["host"],
                                     glob.conf.config["db"]["username"],
                                     glob.conf.config["db"]["password"],
                                     glob.conf.config["db"]["database"],
                                     int(glob.conf.config["db"]["workers"]))
            consoleHelper.printNoNl(" ")
            consoleHelper.printDone()
        except:
            # Exception while connecting to db
            consoleHelper.printError()
            consoleHelper.printColored(
                "[!] Error while connection to database. Please check your config.ini and run the server again",
                bcolors.RED)
            raise

        # Connect to redis
        try:
            consoleHelper.printNoNl("> Connecting to redis... ")
            glob.redis = redis.Redis(glob.conf.config["redis"]["host"],
                                     glob.conf.config["redis"]["port"],
                                     glob.conf.config["redis"]["database"],
                                     glob.conf.config["redis"]["password"])
            glob.redis.ping()
            consoleHelper.printNoNl(" ")
            consoleHelper.printDone()
        except:
            # Exception while connecting to db
            consoleHelper.printError()
            consoleHelper.printColored(
                "[!] Error while connection to redis. Please check your config.ini and run the server again",
                bcolors.RED)
            raise

        # Empty redis cache
        #TODO: do we need this?
        try:
            glob.redis.eval(
                "return redis.call('del', unpack(redis.call('keys', ARGV[1])))",
                0, "lets:*")
        except redis.exceptions.ResponseError:
            # Script returns error if there are no keys starting with peppy:*
            pass

        # Save lets version in redis
        glob.redis.set("lets:version", glob.VERSION)

        # Create threads pool
        try:
            consoleHelper.printNoNl("> Creating threads pool... ")
            glob.pool = ThreadPool(int(glob.conf.config["server"]["threads"]))
            consoleHelper.printDone()
        except:
            consoleHelper.printError()
            consoleHelper.printColored(
                "[!] Error while creating threads pool. Please check your config.ini and run the server again",
                bcolors.RED)

        # Load achievements
        consoleHelper.printNoNl("> Loading achievements... ")
        try:
            achievements = glob.db.fetchAll("SELECT * FROM achievements")
            for achievement in achievements:
                condition = eval(
                    f"lambda score, mode_vn, stats: {achievement.pop('cond')}")
                glob.achievements.append(
                    Achievement(_id=achievement['id'],
                                file=achievement['icon'],
                                name=achievement['name'],
                                desc=achievement['description'],
                                cond=condition))
        except Exception as e:
            consoleHelper.printError()
            consoleHelper.printColored(
                "[!] Error while loading achievements! ({})".format(
                    traceback.format_exc()),
                bcolors.RED,
            )
            return 1
        consoleHelper.printDone()

        # Set achievements version
        glob.redis.set("lets:achievements_version", glob.ACHIEVEMENTS_VERSION)
        consoleHelper.printColored(
            "Achievements version is {}".format(glob.ACHIEVEMENTS_VERSION),
            bcolors.YELLOW)

        # Print disallowed mods into console (Used to also assign it into variable but has been moved elsewhere)
        unranked_mods = [
            key for key, value in glob.conf.extra["common"]
            ["rankable-mods"].items() if not value
        ]
        consoleHelper.printColored(
            "Unranked mods: {}".format(", ".join(unranked_mods)),
            bcolors.YELLOW)

        # Print allowed beatmap rank statuses
        allowed_beatmap_rank = [
            key for key, value in glob.conf.extra["lets"]
            ["allowed-beatmap-rankstatus"].items() if value
        ]
        consoleHelper.printColored(
            "Allowed beatmap rank statuses: {}".format(
                ", ".join(allowed_beatmap_rank)), bcolors.YELLOW)

        # Make array of bools to respective rank id's
        glob.conf.extra["_allowed_beatmap_rank"] = [
            getattr(rankedStatuses, key) for key in allowed_beatmap_rank
        ]  # Store the allowed beatmap rank id's into glob

        # Discord
        if generalUtils.stringToBool(glob.conf.config["discord"]["enable"]):
            glob.schiavo = schiavo.schiavo(
                glob.conf.config["discord"]["boturl"], "**lets**")
        else:
            consoleHelper.printColored(
                "[!] Warning! Discord logging is disabled!", bcolors.YELLOW)

        # Check debug mods
        glob.debug = generalUtils.stringToBool(
            glob.conf.config["server"]["debug"])
        if glob.debug:
            consoleHelper.printColored(
                "[!] Warning! Server running in debug mode!", bcolors.YELLOW)

        # Server port
        try:
            serverPort = int(glob.conf.config["server"]["port"])
        except:
            consoleHelper.printColored(
                "[!] Invalid server port! Please check your config.ini and run the server again",
                bcolors.RED)

        # Make app
        glob.application = make_app()

        # Set up sentry
        try:
            glob.sentry = generalUtils.stringToBool(
                glob.conf.config["sentry"]["enable"])
            if glob.sentry:
                glob.application.sentry_client = AsyncSentryClient(
                    glob.conf.config["sentry"]["dsn"], release=glob.VERSION)
            else:
                consoleHelper.printColored(
                    "[!] Warning! Sentry logging is disabled!", bcolors.YELLOW)
        except:
            consoleHelper.printColored(
                "[!] Error while starting Sentry client! Please check your config.ini and run the server again",
                bcolors.RED)

        # Set up Datadog
        try:
            if generalUtils.stringToBool(
                    glob.conf.config["datadog"]["enable"]):
                glob.dog = datadogClient.datadogClient(
                    glob.conf.config["datadog"]["apikey"],
                    glob.conf.config["datadog"]["appkey"])
            else:
                consoleHelper.printColored(
                    "[!] Warning! Datadog stats tracking is disabled!",
                    bcolors.YELLOW)
        except:
            consoleHelper.printColored(
                "[!] Error while starting Datadog client! Please check your config.ini and run the server again",
                bcolors.RED)

        # Connect to pubsub channels
        pubSub.listener(glob.redis, {
            "lets:beatmap_updates": beatmapUpdateHandler.handler(),
        }).start()
        # Prometheus port
        statsPort = None
        try:
            if glob.conf.config["prometheus"]["port"]:
                statsPort = int(glob.conf.config["prometheus"]["port"])
        except:
            consoleHelper.printColored(
                "Invalid stats port! Please check your config.ini and run the server again",
                bcolors.YELLOW)
            raise

        if statsPort:
            consoleHelper.printColored(
                "Stats exporter listening on localhost:{}".format(statsPort),
                bcolors.GREEN)
            prometheus_client.start_http_server(statsPort, addr="127.0.0.1")

        # Server start message and console output
        consoleHelper.printColored(
            "> L.E.T.S. is listening for clients on {}:{}...".format(
                glob.conf.config["server"]["host"], serverPort), bcolors.GREEN)

        # Start Tornado
        glob.application.listen(serverPort,
                                address=glob.conf.config["server"]["host"])
        tornado.ioloop.IOLoop.instance().start()

    finally:
        # Perform some clean up
        print("> Disposing server... ")
        glob.fileBuffers.flushAll()
        consoleHelper.printColored("Goodbye!", bcolors.GREEN)

    return 0