Beispiel #1
0
async def logout(_, token: 'Player'):
    if (time.time() - token.login_time) < 5:
        # weird osu scheme that all already knows
        return

    await token.logout()
    logger.klog(f"[{token.name}] Leaved kuriso!")
    return True
Beispiel #2
0
async def silence(args: List[str], player: 'Player', _):
    if args:
        if len(args) < 2:
            return 'You need put amount'
        if len(args) < 3 or args[2].lower() not in ['s', 'm', 'h', 'd']:
            return 'You need to set unit s/m/h/d'
        if len(args) < 4:
            return 'You need to put reason'
    else:
        return 'I need nickname who you want to silence'

    target = args[0]
    amount = args[1]
    unit = args[2]
    reason = ' '.join(args[3:]).strip()
    if not amount.isdigit():
        return "The amount must be a number."

    # Calculate silence seconds
    if unit == 's':
        silenceTime = int(amount)
    elif unit == 'm':
        silenceTime = int(amount) * 60
    elif unit == 'h':
        silenceTime = int(amount) * 3600
    elif unit == 'd':
        silenceTime = int(amount) * 86400
    else:
        return "Invalid time unit (s/m/h/d)."

    # Max silence time is 7 days
    if silenceTime > 604800:
        return "Invalid silence time. Max silence time is 7 days."

    to_token = Context.players.get_token(name=target.lower())
    if to_token:
        if to_token.id == player.id or \
                to_token.privileges >= player.privileges:
            return 'You can\'t silence that dude'

        await to_token.silence(silenceTime, reason, player.id)
        logger.klog(f"[Player/{to_token.name}] has been silenced for following reason: {reason}")
        return 'User silenced'

    offline_user = await userHelper.get_start_user(target.lower())
    if offline_user['privileges'] > player.privileges:
        return 'You can\'t silence that dude'

    if not offline_user:
        return 'User not found!'

    res = await userHelper.silence(offline_user['id'], silenceTime, reason, player.id)
    if not res:
        return 'Not silenced!'

    logger.klog(f"[Player/{offline_user['username']}] has been silenced for following reason: {reason}")
    return 'User successfully silenced'
Beispiel #3
0
async def activate_user(user_id: int, user_name: str,
                        hashes: Union[Tuple[str], List[str]]) -> bool:
    if len(hashes) < 5 or not all((x for x in hashes)):
        logger.elog(
            f"[Verification/{user_id}] have wrong hash set! Probably generated by randomizer"
        )
        return False

    match: dict
    if hashes[2] == "b4ec3c4334a0249dae95c284ec5983df" or \
            hashes[4] == "ffae06fb022871fe9beb58b005c5e21d":
        # user logins from wine(old bancho checks)
        match = await Context.mysql.fetch(
            "select userid from hw_user where unique_id = %(unique_id)s and userid != %(userid)s and activated = 1 limit 1",
            {
                'unique_id': hashes[3],
                'userid': user_id
            })
    else:
        # its 100%(prob 80%) windows
        match = await Context.mysql.fetch(
            'select userid from hw_user '
            'where mac = %(mac)s and unique_id = %(unique_id)s '
            'and disk_id = %(disk_id)s '
            'and userid != %(userid)s '
            'and activated = 1 LIMIT 1', {
                "mac": hashes[2],
                "unique_id": hashes[3],
                "disk_id": hashes[4],
                "userid": user_id
            })

    await Context.mysql.execute(
        'update users set privileges = privileges & %s where id = %s limit 1',
        [~Privileges.USER_PENDING_VERIFICATION, user_id])
    if match:
        source_user_id = match['userid']
        source_user_name = (await get_username(source_user_id))

        # баним его
        await ban(source_user_id)
        # уведомляем стафф, что это читерюга и как-бы ну нафиг.
        await append_notes(user_id, [
            f"{source_user_name}\'s multiaccount ({hashes[2:5]}),found HWID match while verifying account ({user_id})"
        ])
        await append_notes(
            source_user_id,
            [f"Has created multiaccount {user_name} ({user_id})"])
        logger.klog(
            f"[{source_user_name}] Has created multiaccount {user_name} ({user_id})"
        )
        return False

    await Context.mysql.execute(
        "UPDATE users SET privileges = privileges | %s WHERE id = %s LIMIT 1",
        [(Privileges.USER_PUBLIC | Privileges.USER_NORMAL), user_id])
    return True
Beispiel #4
0
    async def send_message(self, message: 'Message') -> bool:
        message.body = f'{message.body[:2045]}...' if message.body[
            2048:] else message.body

        chan: str = message.to
        if chan.startswith("#"):
            channel: 'Channel' = Context.channels.get(chan, None)
            if not channel:
                logger.klog(
                    f"[{self.name}/Bot] Tried to send message in unknown channel. Ignoring it..."
                )
                return False

            logger.klog(
                f"{self.name}({self.id})/Bot -> {channel.server_name}: {bytes(message.body, 'latin_1').decode()}"
            )
            await channel.send_message(self.id, message)
            return True

        # DM
        receiver = Context.players.get_token(
            name=message.to.lower().strip().replace(" ", "_"))
        if not receiver:
            logger.klog(f"[{self.name}] Tried to offline user. Ignoring it...")
            return False

        logger.klog(
            f"#DM {self.name}({self.id})/Bot -> {message.to}({receiver.id}): {bytes(message.body, 'latin_1').decode()}"
        )
        receiver.enqueue(await PacketBuilder.BuildMessage(self.id, message))
        return True
async def send_private_message(packet_data: bytes, token: 'Player'):
    if token.silenced:
        logger.klog(
            f"[{token.name}] This bruh tried to send message, when he is muted"
        )
        return False

    message = await PacketResolver.read_message(packet_data)
    message.client_id = token.id
    await token.send_message(
        Message(sender=token.name,
                body=message.body,
                to=message.to,
                client_id=token.id))

    await CrystalBot.proceed_command(message)
    return True
Beispiel #6
0
async def remove_silence(args: List[str], player: 'Player', _):
    if not args:
        return 'I need nickname who you want to remove the silence'

    target = args[0]

    to_token = Context.players.get_token(name=target.lower())
    if to_token:
        logger.klog(f"[Player/{to_token.name}] silence reset")
        await to_token.silence(0, "", player.id)
        return 'User silenced'

    offline_user = await userHelper.get_start_user(target.lower())
    if not offline_user:
        return 'User not found!'

    res = await userHelper.silence(offline_user['id'], 0, "", player.id)
    if not res:
        return 'Not silenced!'

    logger.klog(f"[Player/{offline_user['username']}] silence reset")
    return 'User successfully silenced'
Beispiel #7
0
    async def leave_channel(self, p: 'Player') -> bool:
        if p not in self.users:
            return False

        # enqueue leave channel
        p.enqueue(await PacketBuilder.PartChannel(self.name))
        self.users.pop(self.users.index(p))
        logger.klog(f"[{p.name}] Parted from {self.server_name}")

        # now we need update channel stats
        if self.temp_channel:
            receivers = self.users
        else:
            receivers = Context.players.get_all_tokens()
        for receiver in receivers:
            receiver.enqueue(await PacketBuilder.ChannelAvailable(self))

        if len(self.users) < 1 and self.temp_channel:
            # clean channel because all left and channel is temp(for multi lobby or spectator)
            Context.channels.pop(self.server_name)

        return True
Beispiel #8
0
    async def join_channel(self, p: 'Player') -> bool:
        if p in self.users:
            p.enqueue(await PacketBuilder.SuccessJoinChannel(self.name))
            return True

        if not self.can_read and not self.is_privileged(p.privileges):
            logger.klog(
                f"[{p.name}] Tried to join private channel {self.server_name} but haven't enough staff "
                "permissions")
            return False

        # enqueue join channel
        p.enqueue(await PacketBuilder.SuccessJoinChannel(self.name))
        self.users.append(p)
        logger.klog(f"[{p.name}] Joined to {self.server_name}")

        # now we need update channel stats
        if self.temp_channel:
            receivers = self.users
        else:
            receivers = Context.players.get_all_tokens()
        for receiver in receivers:
            receiver.enqueue(await PacketBuilder.ChannelAvailable(self))
        return True
Beispiel #9
0
    async def send_message(self, message: 'Message') -> bool:
        message.body = f'{message.body[:2045]}...' if message.body[
            2048:] else message.body

        chan: str = message.to
        if chan.startswith("#"):
            # this is channel object
            if chan.startswith("#multi"):
                if self.is_tourneymode:
                    if self.id_tourney > 0:
                        chan = f"#multi_{self.id_tourney}"
                    else:
                        return False
                else:
                    chan = f"#multi_{self.match.id}"
            elif chan.startswith("#spec"):
                if self.spectating:
                    chan = f"#spec_{self.spectating.id}"
                else:
                    chan = f"#spec_{self.id}"

            channel: 'Channel' = Context.channels.get(chan, None)
            if not channel:
                logger.klog(
                    f"[{self.name}] Tried to send message in unknown channel. Ignoring it..."
                )
                return False

            self.user_chat_log.append(message)
            logger.klog(
                f"{self.name}({self.id}) -> {channel.server_name}: {bytes(message.body, 'latin_1').decode()}"
            )
            await channel.send_message(self.id, message)
            return True

        # DM
        receiver = Context.players.get_token(
            name=message.to.lower().strip().replace(" ", "_"))
        if not receiver:
            logger.klog(f"[{self.name}] Tried to offline user. Ignoring it...")
            return False

        if receiver.pm_private and self.id not in receiver.friends:
            self.enqueue(await PacketBuilder.PMBlocked(message.to))
            logger.klog(
                f"[{self.name}] Tried message {message.to} which has private PM."
            )
            return False

        if self.pm_private and receiver.id not in self.friends:
            self.pm_private = False
            logger.klog(
                f"[{self.name}] which has private pm sended message to non-friend user. PM unlocked"
            )

        if receiver.silenced:
            self.enqueue(await PacketBuilder.TargetSilenced(message.to))
            logger.klog(
                f'[{self.name}] Tried message {message.to}, but has been silenced.'
            )
            return False

        self.user_chat_log.append(message)
        logger.klog(
            f"#DM {self.name}({self.id}) -> {message.to}({receiver.id}): {bytes(message.body, 'latin_1').decode()}"
        )

        receiver.enqueue(await PacketBuilder.BuildMessage(self.id, message))
        return True
Beispiel #10
0
async def main_handler(request: Request):
    if request.headers.get("user-agent", "") != "osu!":
        return HTMLResponse(f"<html>{Context.motd_html}</html>")

    token = request.headers.get("osu-token", None)
    if token:
        if token == '':
            response = await PacketBuilder.UserID(-5)
            return BanchoResponse(response)  # send to re-login

        token_object = Context.players.get_token(token=token)
        if not token_object:
            # send to re-login, because token doesn't exists in storage
            response = await PacketBuilder.UserID(-5)
            return BanchoResponse(response)

        token_object.last_packet_unix = int(time.time())

        # packets recieve
        raw_bytes = KurisoBuffer(None)
        await raw_bytes.write_to_buffer(await request.body())

        response = bytearray()
        while not raw_bytes.EOF():
            packet_id = await raw_bytes.read_u_int_16()
            _ = await raw_bytes.read_int_8()  # empty byte
            packet_length = await raw_bytes.read_int_32()

            if packet_id == OsuPacketID.Client_Pong.value:
                # client just spamming it and tries to say, that he is normal :sip:
                continue

            data = await raw_bytes.slice_buffer(packet_length)

            if token_object.is_restricted and packet_id not in ALLOWED_RESTRICT_PACKETS:
                logger.wlog(
                    f"[{token_object.token}/{token_object.name}] Ignored packet {packet_id}(account restrict)"
                )
                continue

            if packet_id in OsuEvent.handlers:
                # This packet can be handled by OsuEvent Class, call it now!
                # Oh wait let go this thing in async executor.
                await OsuEvent.handlers[packet_id](data, token_object)
                logger.klog(
                    f"[{token_object.token}/{token_object.name}] Has triggered {OsuPacketID(packet_id)} with packet length: {packet_length}"
                )
            else:
                logger.wlog(
                    f"[Events] Packet ID: {packet_id} not found in events handlers"
                )

        response += token_object.dequeue()

        response = BanchoResponse(bytes(response), token=token_object.token)
        return response
    else:
        # first login
        # 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
        start_time = time.time()  # auth speed benchmark time

        loginData = (await request.body()).decode().split("\n")
        if len(loginData) < 3:
            return BanchoResponse(await PacketBuilder.UserID(-5))

        if not await userHelper.check_login(loginData[0], loginData[1],
                                            request.client.host):
            logger.elog(
                f"[{loginData[0]}] tried to login but failed with password")
            return BanchoResponse(await PacketBuilder.UserID(-1))

        user_data = await userHelper.get_start_user(loginData[0])
        if not user_data:
            return BanchoResponse(await PacketBuilder.UserID(-1))

        data = loginData[2].split("|")
        hashes = data[3].split(":")[:-1]
        time_offset = int(data[1])
        pm_private = data[4] == '1'

        isTourney = "tourney" in data[0]

        # check if user already on kuriso
        if Context.players.get_token(uid=user_data['id']) and not isTourney:
            # wtf osu
            await Context.players.get_token(uid=user_data['id']).logout()

        if (user_data['privileges'] & Privileges.USER_PENDING_VERIFICATION) or \
                not await userHelper.user_have_hardware(user_data['id']):
            # we need to verify our user
            is_success_verify = await userHelper.activate_user(
                user_data['id'], user_data['username'], hashes)
            if not is_success_verify:
                response = (await PacketBuilder.UserID(
                    -1
                ) + await PacketBuilder.Notification(
                    'Your HWID is not clear. Contact Staff to create account!')
                            )
                return BanchoResponse(bytes(response))
            else:
                user_data['privileges'] = KurikkuPrivileges.Normal.value
                await Context.mysql.execute(
                    "UPDATE hw_user SET activated = 1 WHERE userid = %s AND mac = %s AND unique_id = %s AND disk_id = %s",
                    [user_data['id'], hashes[2], hashes[3], hashes[4]])

        if (user_data["privileges"] & KurikkuPrivileges.Normal) != KurikkuPrivileges.Normal and \
                (user_data["privileges"] & Privileges.USER_PENDING_VERIFICATION) == 0:
            logger.elog(f"[{loginData}] Banned chmo tried to login")
            response = (await PacketBuilder.UserID(
                -1
            ) + await PacketBuilder.Notification(
                'You are banned. Join our discord for additional information.')
                        )

            return BanchoResponse(bytes(response))

        if ((user_data["privileges"] & Privileges.USER_PUBLIC > 0) and (user_data["privileges"] & Privileges.USER_NORMAL == 0)) \
            and (user_data["privileges"] & Privileges.USER_PENDING_VERIFICATION) == 0:
            logger.elog(f"[{loginData}] Locked dude tried to login")
            response = (
                await PacketBuilder.UserID(-1) +
                await PacketBuilder.Notification(
                    'You are locked by staff. Join discord and ask for unlock!'
                ))

            return BanchoResponse(bytes(response))

        if bool(Context.bancho_settings['bancho_maintenance']):
            # send to user that maintenance
            if not (user_data['privileges'] & KurikkuPrivileges.Developer):
                response = (await PacketBuilder.UserID(
                    -1
                ) + await PacketBuilder.Notification(
                    'Kuriso! is in maintenance mode. Please try to login again later.'
                ))

                return BanchoResponse(bytes(response))

        await Context.mysql.execute(
            '''
            INSERT INTO hw_user (userid, mac, unique_id, disk_id, occurencies) VALUES (%s, %s, %s, %s, 1)
            ON DUPLICATE KEY UPDATE occurencies = occurencies + 1''',
            [user_data['id'], hashes[2], hashes[3], hashes[4]]
        )  # log hardware и не ебёт что

        osu_version = data[0]
        await userHelper.setUserLastOsuVer(user_data['id'], osu_version)
        osuVersionInt = osu_version[1:9]

        now = datetime.datetime.now()
        vernow = datetime.datetime(int(osuVersionInt[:4]),
                                   int(osuVersionInt[4:6]),
                                   int(osuVersionInt[6:8]), 00, 00)
        deltanow = now - vernow

        if not osuVersionInt[0].isdigit() or \
                deltanow.days > 360 or int(osuVersionInt) < 20200811:
            response = (await PacketBuilder.UserID(
                -2
            ) + await PacketBuilder.Notification(
                'Sorry, you use outdated/bad osu!version. Please update your game to join server'
            ))
            return BanchoResponse(bytes(response))

        if isTourney:
            if Context.players.get_token(uid=user_data['id']):
                # manager was logged before, we need just add additional token
                token, player = Context.players.get_token(
                    uid=user_data['id']).add_additional_client()
            else:
                player = TourneyPlayer(
                    int(user_data['id']),
                    user_data['username'],
                    user_data['privileges'],
                    time_offset,
                    pm_private,
                    0 if user_data['silence_end'] - int(time.time()) < 0 else
                    user_data['silence_end'] - int(time.time()),
                    is_tourneymode=True,
                    ip=request.client.host)
                await asyncio.gather(*[
                    player.parse_friends(),
                    player.update_stats(),
                    player.parse_country(request.client.host)
                ])
        else:
            # create Player instance finally!!!!
            player = Player(
                int(user_data['id']),
                user_data['username'],
                user_data['privileges'],
                time_offset,
                pm_private,
                0 if user_data['silence_end'] - int(time.time()) < 0 else
                user_data['silence_end'] - int(time.time()),
                ip=request.client.host)

            await asyncio.gather(*[
                player.parse_friends(),
                player.update_stats(),
                player.parse_country(request.client.host)
            ])

        if "ppy.sh" in request.url.netloc and not (
                player.is_admin or
            (player.privileges & KurikkuPrivileges.TournamentStaff
             == KurikkuPrivileges.TournamentStaff)):
            return BanchoResponse(
                bytes(await PacketBuilder.UserID(
                    -5
                ) + await PacketBuilder.Notification(
                    'Sorry, you use outdated connection to server. Please use devserver flag'
                )))

        user_country = await userHelper.get_country(user_data['id'])
        if user_country == "XX":
            await userHelper.set_country(user_data['id'], player.country[1])

        start_bytes_async = await asyncio.gather(*[
            PacketBuilder.UserID(player.id),
            PacketBuilder.ProtocolVersion(19),
            PacketBuilder.BanchoPrivileges(player.bancho_privs),
            PacketBuilder.UserPresence(player),
            PacketBuilder.UserStats(player),
            PacketBuilder.FriendList(player.friends),
            PacketBuilder.SilenceEnd(
                player.silence_end if player.silence_end > 0 else 0),
            PacketBuilder.Notification(
                f'''Welcome to kuriso!\nBuild ver: v{Context.version}\nCommit: {Context.commit_id}'''
            ),
            PacketBuilder.Notification(
                f'Authorization took: {round((time.time() - start_time) * 1000, 4)}ms'
            )
        ])
        start_bytes = b''.join(start_bytes_async)

        if Context.bancho_settings.get('login_notification', None):
            start_bytes += await PacketBuilder.Notification(
                Context.bancho_settings.get('login_notification', None))

        if Context.bancho_settings.get('bancho_maintenance', None):
            start_bytes += await PacketBuilder.Notification(
                'Don\'t forget enable server after maintenance :sip:')

        if Context.bancho_settings['menu_icon']:
            start_bytes += await PacketBuilder.MainMenuIcon(
                Context.bancho_settings['menu_icon'])

        if isTourney and Context.players.get_token(uid=user_data['id']):
            logger.klog(
                f"[{player.token}/{player.name}] Joined kuriso as additional client for origin!"
            )
            for p in Context.players.get_all_tokens():
                if p.is_restricted:
                    continue

                start_bytes += bytes(await PacketBuilder.UserPresence(p) +
                                     await PacketBuilder.UserStats(p))
        else:
            for p in Context.players.get_all_tokens():
                if p.is_restricted:
                    continue

                start_bytes += bytes(await PacketBuilder.UserPresence(p) +
                                     await PacketBuilder.UserStats(p))
                p.enqueue(
                    bytes(await PacketBuilder.UserPresence(player) +
                          await PacketBuilder.UserStats(player)))

            await userHelper.saveBanchoSession(player.id, request.client.host)

            Context.players.add_token(player)
            await Context.redis.set("ripple:online_users",
                                    len(Context.players.get_all_tokens(True)))
            logger.klog(f"[{player.token}/{player.name}] Joined kuriso!")

        # default channels to join is #osu, #announce and #english
        await asyncio.gather(*[
            Context.channels['#osu'].join_channel(
                player), Context.channels['#announce'].join_channel(player),
            Context.channels['#english'].join_channel(player)
        ])

        for (_, chan) in Context.channels.items():
            if not chan.temp_channel and chan.can_read:
                start_bytes += await PacketBuilder.ChannelAvailable(chan)

        start_bytes += await PacketBuilder.ChannelListeningEnd()

        if player.is_restricted:
            start_bytes += await PacketBuilder.UserRestricted()
            await CrystalBot.ez_message(
                player.name,
                "Your account is currently in restricted mode. Please visit kurikku's website for more information."
            )

        Context.stats['osu_versions'].labels(osu_version=osu_version).inc()
        Context.stats['devclient_usage'].labels(host=request.url.netloc).inc()
        return BanchoResponse(start_bytes, player.token)
Beispiel #11
0
async def sub_reader(ch: aioredis.Channel):
    while await ch.wait_message():
        if ch.name in MAPPED_FUNCTIONS:
            logger.klog(f"[Redis/Pubsub] Received event in {ch.name}")
            await MAPPED_FUNCTIONS[ch.name](ch)
Beispiel #12
0
async def main():
    # load dotenv file
    load_dotenv(find_dotenv())

    # Load configuration for our project
    Config.load_config()
    logger.slog("[Config] Loaded")

    # create simple Starlette through uvicorn app
    app = Starlette(debug=Config.config['debug'])
    app.add_middleware(ProxyHeadersMiddleware)

    if Config.config['sentry']['enabled']:
        sentry_sdk.init(dsn=Config.config['sentry']['url'])
        app.add_middleware(SentryMiddleware)

    # load version
    Context.load_version()
    logger.klog(f"Hey! Starting kuriso! v{Context.version} (commit-id: {Context.commit_id})")
    logger.printColored(open("kuriso.MOTD", mode="r", encoding="utf-8").read(), logger.YELLOW)

    # Load all events & handlers
    registrator.load_handlers(app)

    # Create Redis connection :sip:
    logger.wlog("[Redis] Trying connection to Redis")

    redis_values = dict(
        db=Config.config['redis']['db'],
        minsize=5,
        maxsize=10
    )
    if Config.config['redis']['password']:
        redis_values['password'] = Config.config['redis']['password']
    
    redis_pool = await aioredis.create_redis_pool(
        f"redis://{Config.config['redis']['host']}",
        **redis_values
    )

    Context.redis = redis_pool
    logger.slog("[Redis] Connection to Redis established! Well done!")

    logger.slog("[Redis] Removing old information about redis...")
    try:
        await Context.redis.set("ripple:online_users", "0")
        redis_flush_script = '''
local matches = redis.call('KEYS', ARGV[1])

local result = 0
for _,key in ipairs(matches) do
    result = result + redis.call('DEL', key)
end

return result
'''
        await Context.redis.eval(redis_flush_script, args=["peppy:*"])
        await Context.redis.eval(redis_flush_script, args=["peppy:sessions:*"])
    except Exception as e:
        traceback.print_exc()
        capture_exception(e)
        logger.elog("[Redis] initiation data ruined... Check this!")

    await Context.redis.set("peppy:version", Context.version)

    logger.wlog("[MySQL] Making connection to MySQL Database...")
    mysql_pool = AsyncSQLPoolWrapper()
    await mysql_pool.connect(**{
        'host': Config.config['mysql']['host'],
        'user': Config.config['mysql']['user'],
        'password': Config.config['mysql']['password'],
        'port': Config.config['mysql']['port'],
        'db': Config.config['mysql']['database'],
        'loop': asyncio.get_event_loop(),
        'autocommit': True
    })
    Context.mysql = mysql_pool
    logger.slog("[MySQL] Connection established!")

    if Config.config['prometheus']['enabled']:
        logger.wlog("[Prometheus stats] Loading...")
        prometheus_client.start_http_server(
            Config.config['prometheus']['port'], 
            addr=Config.config['prometheus']['host']
        )
        logger.slog("[Prometheus stats] Metrics started...")

    # now load bancho settings
    await Context.load_bancho_settings()
    await registrator.load_default_channels()

    from bot.bot import CrystalBot
    # now load bot
    await CrystalBot.connect()
    # and register bot commands
    CrystalBot.load_commands()

    logging.getLogger('apscheduler.executors.default').setLevel(logging.WARNING)

    scheduler = AsyncIOScheduler()
    scheduler.start()
    scheduler.add_job(loops.clean_timeouts, "interval", seconds=60)
    if Config.config['prometheus']['enabled']:
        scheduler.add_job(loops.add_prometheus_stats, "interval", seconds=15)
    scheduler.add_job(loops.add_stats, "interval", seconds=120)

    # Setup pub/sub listeners for LETS/old admin panel events
    event_loop = asyncio.get_event_loop()
    event_loop.create_task(pubsub_listeners.init())

    Context.load_motd()
    uvicorn.run(app, host=Config.config['host']['address'], port=Config.config['host']['port'], access_log=False)