Esempio n. 1
0
async def on_start() -> None:
    glob.http = aiohttp.ClientSession(json_serialize=orjson.dumps)

    # connect to mysql
    glob.db = cmyui.AsyncSQLPool()
    await glob.db.connect(glob.config.mysql)

    # run the sql updater
    updater = Updater(glob.version)
    await updater.run()
    await updater.log_startup()

    # create our bot & append it to the global player list.
    glob.bot = Player(id=1, name='Aika', priv=Privileges.Normal)
    glob.bot.last_recv_time = float(0x7fffffff)

    glob.players.append(glob.bot)

    # add all channels from db.
    async for chan in glob.db.iterall('SELECT * FROM channels'):
        chan['read_priv'] = Privileges(chan.pop('read_priv', 1))
        chan['write_priv'] = Privileges(chan.pop('write_priv', 2))
        glob.channels.append(Channel(**chan))

    # add all mappools from db.
    async for pool in glob.db.iterall('SELECT * FROM tourney_pools'):
        # overwrite basic types with some class types
        creator = await glob.players.get(id=pool['created_by'], sql=True)
        pool['created_by'] = creator  # replace id with player object

        pool = MapPool(**pool)
        await pool.maps_from_sql()
        glob.pools.append(pool)

    # add new donation ranks & enqueue tasks to remove current ones.
    # TODO: this system can get quite a bit better; rather than just
    # removing, it should rather update with the new perks (potentially
    # a different tier, enqueued after their current perks).

    async def rm_donor(userid: int, delay: int):
        await asyncio.sleep(delay)

        p = await glob.players.get(id=userid, sql=True)
        await p.remove_privs(Privileges.Donator)

        log(f"{p}'s donation perks have expired.", Ansi.MAGENTA)

    query = ('SELECT id, donor_end FROM users '
             'WHERE donor_end > UNIX_TIMESTAMP()')

    async for donation in glob.db.iterall(query):
        # calculate the delta between now & the exp date.
        delta = donation['donor_end'] - time.time()

        if delta > (60 * 60 * 24 * 30):
            # ignore donations expiring in over a months time;
            # the server should restart relatively often anyways.
            continue

        asyncio.create_task(rm_donor(donation['id'], delta))
Esempio n. 2
0
 async def prepare(cls) -> None:
     """Fetch data from sql & return; preparing to run the server."""
     log('Fetching channels from sql', Ansi.LCYAN)
     return cls(
         Channel(name=row['name'],
                 topic=row['topic'],
                 read_priv=Privileges(row['read_priv']),
                 write_priv=Privileges(row['write_priv']),
                 auto_join=row['auto_join'] == 1)
         for row in await glob.db.fetchall('SELECT * FROM channels'))
Esempio n. 3
0
 async def prepare(cls, db_cursor: aiomysql.DictCursor) -> 'Channels':
     """Fetch data from sql & return; preparing to run the server."""
     log('Fetching channels from sql.', Ansi.LCYAN)
     await db_cursor.execute('SELECT * FROM channels')
     return cls([
         Channel(name=row['name'],
                 topic=row['topic'],
                 read_priv=Privileges(row['read_priv']),
                 write_priv=Privileges(row['write_priv']),
                 auto_join=row['auto_join'] == 1) async for row in db_cursor
     ])
Esempio n. 4
0
 async def prepare(cls, db_cursor: aiomysql.DictCursor) -> "Channels":
     """Fetch data from sql & return; preparing to run the server."""
     log("Fetching channels from sql.", Ansi.LCYAN)
     await db_cursor.execute("SELECT * FROM channels")
     return cls([
         Channel(
             name=row["name"],
             topic=row["topic"],
             read_priv=Privileges(row["read_priv"]),
             write_priv=Privileges(row["write_priv"]),
             auto_join=row["auto_join"] == 1,
         ) async for row in db_cursor
     ], )
Esempio n. 5
0
async def setpriv(p: 'Player', c: Messageable, msg: Sequence[str]) -> str:
    """Set privileges for a specified player (by name)."""
    if len(msg) < 2:
        return 'Invalid syntax: !setpriv <name> <role1 role2 role3 ...>'

    priv = Privileges(0)

    for m in msg[1:]:
        if not (_priv := str_priv_dict[m]):
            return f'Not found: {m}.'

        priv |= _priv
Esempio n. 6
0
 async def wrapper(*, sender: Dict[str, Any], match_id: int,
                   **kwargs) -> Any:
     # TODO: Walrus
     can = Privileges(sender["privileges"]).has(
         Privileges.USER_TOURNAMENT_STAFF)
     if not can:
         match_info = await singletons.bot.Bot(
         ).bancho_api_client.get_match_info(match_id)
         can = match_info["host_api_identifier"] == sender["api_identifier"] \
             or match_info["api_owner_user_id"] == sender["user_id"]
     if not can:
         return "You must be the host of the match to trigger this command."
     return await f(sender=sender, match_id=match_id, **kwargs)
Esempio n. 7
0
    def __init__(self, **kwargs) -> None:
        self.token: str = kwargs.get('token', str(uuid.uuid4()))
        self.id: Optional[int] = kwargs.get('id', None)
        self.name: Optional[str] = kwargs.get('name', None)
        self.safe_name = self.make_safe(self.name) if self.name else None
        self.priv = Privileges(kwargs.get('priv', Privileges.Normal))

        self.stats = {mode: ModeData() for mode in GameMode}
        self.status = Status()

        self.friends = set()  # userids, not player objects
        self.channels = []
        self.spectators = []
        self.spectating: Optional[Player] = None
        self.match: Optional[Match] = None

        # Store most recent score for each gamemode.
        self.recent_scores = {mode: None for mode in GameMode}

        # Store the last beatmap /np'ed by the user.
        self.last_np: Optional[Beatmap] = None

        self.country = (0, 'XX')  # (code , letters)
        self.location = (0.0, 0.0)  # (lat, long)

        self.utc_offset: int = kwargs.get('utc_offset', 0)
        self.pm_private: bool = kwargs.get('pm_private', False)

        self.away_msg: Optional[str] = None
        self.silence_end: int = kwargs.get('silence_end', 0)
        self.in_lobby = False

        _ctime = int(time.time())
        self.login_time = _ctime
        self.ping_time = _ctime

        self.osu_version = kwargs.get('osu_version', None)
        self.pres_filter = PresenceFilter.Nil

        # XXX: below is mostly gulag-specific & internal stuff

        # {id: {'callback', func, 'timeout': unixt, 'reusable': False}, ...}
        self.menu_options: dict[int, dict[str, Any]] = {}

        # Packet queue
        self._queue = asyncio.Queue()
Esempio n. 8
0
    def __init__(self, **kwargs) -> None:
        self.token: str = kwargs.get('token',
                                     ''.join(choices(ascii_lowercase, k=32)))
        self.id: Optional[int] = kwargs.get('id', None)
        self.name: Optional[str] = kwargs.get('name', None)
        self.safe_name: Optional[str] = self.ensure_safe(
            self.name) if self.name else None
        self.priv = Privileges(kwargs.get('priv', Privileges.Normal))

        self.rx = False  # stored for ez use
        self.stats = [ModeData() for _ in range(7)]
        self.status = Status()

        self.friends = set()  # userids, not player objects
        self.channels = []
        self.spectators = []
        self.spectating: Optional[Player] = None
        self.match: Optional[Match] = None

        # Store most recent score for each gamemode.
        self.recent_scores = [None for _ in range(7)]

        # Store the last beatmap /np'ed by the user.
        self.last_np: Optional[Beatmap] = None

        self.country = (0, 'XX')  # (code , letters)
        self.location = (0.0, 0.0)  # (lat, long)

        self.utc_offset: int = kwargs.get('utc_offset', 0)
        self.pm_private: bool = kwargs.get('pm_private', False)

        self.away_msg: Optional[str] = None
        self.silence_end: int = kwargs.get('silence_end', 0)
        self.in_lobby = False

        c_time = int(time())
        self.login_time = c_time
        self.ping_time = c_time
        del c_time

        self.pres_filter = PresenceFilter.Nil

        # Packet queue
        self._queue = SimpleQueue()
Esempio n. 9
0
 async def wrapper(*, sender: Dict[str, Any], **kwargs) -> Any:
     if not Privileges(sender["privileges"]).has(required_privileges):
         return "You don't have the required privileges to trigger this command."
     return await f(sender=sender, **kwargs)
Esempio n. 10
0
                return p

        if not sql:
            # don't fetch from sql
            # if not specified
            return

        # try to get from sql.
        res = await glob.db.fetch(
            'SELECT id, name, priv, pw_bcrypt, silence_end '
            f'FROM users WHERE {attr} = %s', [val])

        if not res:
            return

        priv = Privileges(res.pop('priv'))
        return Player(**res, priv=priv)

    async def get_login(self,
                        name: str,
                        pw_md5: str,
                        sql: bool = False) -> Optional[Player]:
        # only used cached results - the user should have
        # logged into bancho at least once. (This does not
        # mean they're logged in now).

        if not (p := await self.get(name=name, sql=sql)):
            return  # no such player online

        if glob.cache['bcrypt'][p.pw_bcrypt] == pw_md5.encode():
            return p
Esempio n. 11
0
    if len(msg) < 2:
        return 'Invalid syntax: !setpriv <name> <role1 | role2 | ...>'

    # A mess that gets each unique privilege out of msg.
    # TODO: rewrite to be at least a bit more readable..
    priv = [str_to_priv(i) for i in set(''.join(msg[1:]).replace(' ', '').lower().split('|'))]
    if any(x is None for x in priv):
        return 'Invalid privileges.'

    if not (t := await glob.players.get_by_name(msg[0], sql=True)):
        return 'Could not find user.'

    await glob.db.execute('UPDATE users SET priv = %s WHERE id = %s',
                    [newpriv := sum(priv), t.id])

    t.priv = Privileges(newpriv)
    return 'Success.'

# temp command
@command(priv=Privileges.Dangerous, public=False)
async def menu(p: Player, c: Messageable, msg: Sequence[str]) -> str:
    async def callback():
        p.enqueue(await packets.notification('yay!'))

    opt_id = await p.add_to_menu(callback)
    return f'[osu://dl/{opt_id} option]'

# XXX: This actually comes in handy sometimes, I initially
# wrote it completely as a joke, but I might keep it in for
# devs.. Comes in handy when debugging to be able to run something
# like `!ev print(await glob.players.get_by_name('cmyui').status.action)`
Esempio n. 12
0
        if not sql:
            # don't fetch from sql
            # if not specified
            return

        # try to get from sql.
        res = await glob.db.fetch(
            'SELECT id, name, priv, pw_bcrypt, '
            'silence_end, clan_id, clan_rank '
            f'FROM users WHERE {attr} = %s', [val])

        if not res:
            return

        # overwrite some things with classes
        res['priv'] = Privileges(res.pop('priv'))

        if res['clan_id'] != 0:
            res['clan'] = glob.clans.get(id=res['clan_id'])
            res['clan_rank'] = ClanRank(res['clan_rank'])
        else:
            res['clan'] = res['clan_rank'] = None

        return Player(**res)

    async def get_ensure(self, **kwargs) -> Optional[Player]:
        """Try to get player from cache, or sql as fallback."""
        if p := self.get(**kwargs):
            return p
        elif p := await self.get_sql(**kwargs):
            return p
Esempio n. 13
0
async def setup_collections() -> None:
    """Setup & cache many global collections (mostly from sql)."""
    # create our bot & append it to the global player list.
    res = await glob.db.fetch('SELECT name FROM users WHERE id = 1')

    # global players list
    glob.players = PlayerList()

    glob.bot = Player(
        id=1,
        name=res['name'],
        priv=Privileges.Normal,
        last_recv_time=float(0x7fffffff)  # never auto-dc
    )
    glob.players.append(glob.bot)

    # global channels list
    glob.channels = ChannelList()
    async for res in glob.db.iterall('SELECT * FROM channels'):
        chan = Channel(name=res['name'],
                       topic=res['topic'],
                       read_priv=Privileges(res['read_priv']),
                       write_priv=Privileges(res['write_priv']),
                       auto_join=res['auto_join'] == 1)

        glob.channels.append(chan)

    # global matches list
    glob.matches = MatchList()

    # global clans list
    glob.clans = ClanList()
    async for res in glob.db.iterall('SELECT * FROM clans'):
        clan = Clan(**res)

        await clan.members_from_sql()
        glob.clans.append(clan)

    # global mappools list
    glob.pools = MapPoolList()
    async for res in glob.db.iterall('SELECT * FROM tourney_pools'):
        pool = MapPool(id=res['id'],
                       name=res['name'],
                       created_at=res['created_at'],
                       created_by=await
                       glob.players.get_ensure(id=res['created_by']))

        await pool.maps_from_sql()
        glob.pools.append(pool)

    # global achievements (sorted by vn gamemodes)
    glob.achievements = {0: [], 1: [], 2: [], 3: []}
    async for res in glob.db.iterall(
            'SELECT `id`, `file`, `name`, `desc`, `cond`, `mode` FROM achievements'
    ):
        # NOTE: achievement conditions are stored as
        # stringified python expressions in the database
        # to allow for easy custom achievements.
        condition = eval(f'lambda score: {res.pop("cond")}')
        achievement = Achievement(**res, cond=condition)

        # NOTE: achievements are grouped by modes internally.
        glob.achievements[res['mode']].append(achievement)
    """ XXX: Unfinished code for beatmap submission.
Esempio n. 14
0
async def on_start() -> None:
    glob.http = aiohttp.ClientSession(json_serialize=orjson.dumps)

    # connect to mysql
    glob.db = cmyui.AsyncSQLPool()
    await glob.db.connect(glob.config.mysql)

    # run the sql updater
    updater = Updater(glob.version)
    await updater.run()
    await updater.log_startup()

    # create our bot & append it to the global player list.
    glob.bot = Player(id=1, name='Aika', priv=Privileges.Normal)
    glob.bot.last_recv_time = float(0x7fffffff)

    glob.players.append(glob.bot)

    # TODO: this section is getting a bit gross.. :P
    # should be moved and probably refactored pretty hard.

    # add all channels from db.
    async for c_res in glob.db.iterall('SELECT * FROM channels'):
        c_res['read_priv'] = Privileges(c_res.get('read_priv', 1))
        c_res['write_priv'] = Privileges(c_res.get('write_priv', 2))
        glob.channels.append(Channel(**c_res))

    # add all mappools from db.
    async for p_res in glob.db.iterall('SELECT * FROM tourney_pools'):
        # overwrite basic types with some class types
        creator = await glob.players.get(id=p_res['created_by'], sql=True)
        p_res['created_by'] = creator  # replace id with player object

        pool = MapPool(**p_res)
        await pool.maps_from_sql()
        glob.pools.append(pool)

    # add all clans from db.
    async for c_res in glob.db.iterall('SELECT * FROM clans'):
        # fetch all members from sql
        m_res = await glob.db.fetchall(
            'SELECT id, clan_rank '
            'FROM users '
            'WHERE clan_id = %s',
            c_res['id'],
            _dict=False)

        members = set()

        for p_id, clan_rank in m_res:
            if clan_rank == 3:
                c_res['owner'] = p_id

            members.add(p_id)

        glob.clans.append(Clan(**c_res, members=members))

    # add all achievements from db.
    async for a_res in glob.db.iterall('SELECT * FROM achievements'):
        condition = eval(f'lambda score: {a_res.pop("cond")}')
        achievement = Achievement(**a_res, cond=condition)
        glob.achievements[a_res['mode']].append(achievement)
    """ bmsubmit stuff
    # get the latest set & map id offsets for custom maps.
    maps_res = await glob.db.fetch(
        'SELECT id, set_id FROM maps '
        'WHERE server = "gulag" '
        'ORDER BY id ASC LIMIT 1'
    )

    if maps_res:
        glob.gulag_maps = maps_res
    """

    # add new donation ranks & enqueue tasks to remove current ones.
    # TODO: this system can get quite a bit better; rather than just
    # removing, it should rather update with the new perks (potentially
    # a different tier, enqueued after their current perks).
    async def rm_donor(userid: int, delay: int):
        await asyncio.sleep(delay)

        p = await glob.players.get(id=userid, sql=True)
        await p.remove_privs(Privileges.Donator)

        log(f"{p}'s donation perks have expired.", Ansi.MAGENTA)

    query = ('SELECT id, donor_end FROM users '
             'WHERE donor_end > UNIX_TIMESTAMP()')

    async for donation in glob.db.iterall(query):
        # calculate the delta between now & the exp date.
        delta = donation['donor_end'] - time.time()

        if delta > (60 * 60 * 24 * 30):
            # ignore donations expiring in over a months time;
            # the server should restart relatively often anyways.
            continue

        asyncio.create_task(rm_donor(donation['id'], delta))
Esempio n. 15
0
async def match_user_joined(match: Dict[str,
                                        Any], tournament_match: misirlou.Match,
                            user: Dict[str, Any], **_) -> None:
    # Determine the slot of whoever joined the match
    new_user = user
    new_slot = None
    new_slot_idx = None
    for i, slot in enumerate(match["slots"]):
        if slot["user"]["api_identifier"] == new_user["api_identifier"]:
            new_slot = slot
            new_slot_idx = i
            break
    assert new_slot is not None and new_slot_idx is not None

    # Check if they are a player
    the_team = tournament_match.get_user_team(new_user["user_id"])
    if the_team is not None:
        # Team player, move them in the right spot

        # TODO: If there's another client from the same user, kick the old one

        if len(the_team.members_in_match
               ) >= tournament_match.tournament.team_size:
            # Too many players in this team, this player is not needed. Kick them.
            await bot.bancho_api_client.match_kick(
                tournament_match.bancho_match_id, new_user["api_identifier"])
            bot.send_message(
                "Your team is full, please ask one of your teammates "
                "to leave the match if you want to play instead.",
                new_user["api_identifier"])
            return

        # Ok, move them to the right free slot for this team
        # Team A gets the first half of the slots, Team B gets the second half
        first_slot = tournament_match.tournament.team_size * (
            0 if the_team.enum == MisirlouTeam.A else 1)
        new_new_slot_idx = None
        for s_i, the_slot in enumerate(
                match["slots"][first_slot:first_slot +
                               tournament_match.tournament.team_size]):
            if SlotStatus(the_slot["status"]) == SlotStatus.OPEN or (
                    the_slot["user"] is not None
                    and the_slot["user"]["api_identifier"]
                    == new_user["api_identifier"]):
                new_new_slot_idx = first_slot + s_i
                break
        # A slot must exist!
        assert new_new_slot_idx is not None
        # If different slot, move
        if new_slot_idx != new_new_slot_idx:
            await bot.bancho_api_client.match_move_user(
                tournament_match.bancho_match_id, new_user["api_identifier"],
                new_new_slot_idx)
        # Also set the team (always)
        await bot.bancho_api_client.set_team(tournament_match.bancho_match_id,
                                             new_user["api_identifier"],
                                             the_team.enum.bancho_team)

        # Update state
        the_team.members_in_match.add(new_user["user_id"])
        tournament_match.usernames[new_user["user_id"]] = new_user["username"]

        # Greet the user with a notification
        if tournament_match.state == TournamentState.PRE_MATCH:
            await bot.bancho_api_client.alert(
                new_user["api_identifier"],
                f"@@@ {tournament_match.tournament.name} @@@\n\n"
                "Welcome to your tournament match!\nThe match will begin as soon as all the players show up. "
                "Please be ready to start playing and don't go afk. The match is managed by an automated bot. "
                "If you need any kind of assistance you can call a human referee with the command '!t humanref'.\n\n"
                "Have fun and good luck!")

        # Notify the bot if the teams are full
        if len(the_team.members_in_match) == \
                len(tournament_match.team_enum_to_team(the_team.enum.other).members_in_match) == \
                tournament_match.tournament.team_size:
            tournament_match.state = TournamentState.ROLLING
            bot.client.trigger("tournament_match_full",
                               match_id=tournament_match.bancho_match_id)
        return

    # Not a tournament player, allow only refs
    if not Privileges(new_user["privileges"]).has(
            Privileges.USER_TOURNAMENT_STAFF):
        # Who is this person? Who called them?
        await bot.bancho_api_client.match_kick(
            tournament_match.bancho_match_id, new_user["api_identifier"])
        await bot.bancho_api_client.alert(
            new_user["api_identifier"],
            "This is a tournament match and you are not allowed to be in there."
        )
        return

    # Tournament staff, move them in the last free slot so they don't mess up
    # with the tournament client view
    last_free_slot_idx = None
    for slot_idx, slot in reversed(list(enumerate(match["slots"]))):
        if SlotStatus(slot["status"]).has(SlotStatus.OPEN):
            last_free_slot_idx = slot_idx
            break
    if last_free_slot_idx is None:
        # No slot? don't move them, they won't hurt anyone
        plugins.tournament.logger.warning(
            "No more free slots available in the tournament match?")
    else:
        await bot.bancho_api_client.match_move_user(
            tournament_match.bancho_match_id,
            new_user["api_identifier"],
            last_free_slot_idx,
        )