Skip to content

tux.cogs.services.levels

Classes:

Name Description
LevelsService

Classes

LevelsService(bot: Tux)

Bases: Cog

Methods:

Name Description
xp_listener

Listens for messages to process XP gain.

process_xp_gain

Processes XP gain for a member.

is_on_cooldown

Checks if the member is on cooldown.

handle_level_up

Handles the level up process for a member.

update_roles

Updates the roles of a member based on their new level.

try_assign_role

Tries to assign a role to a member.

calculate_xp_for_level

Calculates the XP required for a given level.

calculate_xp_increment

Calculates the XP increment for a member.

calculate_level

Calculates the level based on XP.

valid_xplevel_input

Check if the input is valid.

generate_progress_bar

Generates an XP progress bar based on the current level and XP.

get_level_progress

Get the progress towards the next level.

Source code in tux/cogs/services/levels.py
Python
def __init__(self, bot: Tux) -> None:
    self.bot = bot
    self.db = DatabaseController()
    self.xp_cooldown = CONFIG.XP_COOLDOWN
    self.levels_exponent = CONFIG.LEVELS_EXPONENT
    self.xp_roles = {role["level"]: role["role_id"] for role in CONFIG.XP_ROLES}
    self.xp_multipliers = {role["role_id"]: role["multiplier"] for role in CONFIG.XP_MULTIPLIERS}
    self.max_level = max(item["level"] for item in CONFIG.XP_ROLES)
    self.enable_xp_cap = CONFIG.ENABLE_XP_CAP

Functions

xp_listener(message: discord.Message) -> None async

Listens for messages to process XP gain.

Parameters:

Name Type Description Default
message Message

The message object.

required
Source code in tux/cogs/services/levels.py
Python
@commands.Cog.listener("on_message")
async def xp_listener(self, message: discord.Message) -> None:
    """
    Listens for messages to process XP gain.

    Parameters
    ----------
    message : discord.Message
        The message object.
    """
    if message.author.bot or message.guild is None or message.channel.id in CONFIG.XP_BLACKLIST_CHANNELS:
        return

    prefixes = await get_prefix(self.bot, message)
    if any(message.content.startswith(prefix) for prefix in prefixes):
        return

    member = message.guild.get_member(message.author.id)
    if member is None:
        return

    await self.process_xp_gain(member, message.guild)
process_xp_gain(member: discord.Member, guild: discord.Guild) -> None async

Processes XP gain for a member.

Parameters:

Name Type Description Default
member Member

The member gaining XP.

required
guild Guild

The guild where the member is gaining XP.

required
Source code in tux/cogs/services/levels.py
Python
async def process_xp_gain(self, member: discord.Member, guild: discord.Guild) -> None:
    """
    Processes XP gain for a member.

    Parameters
    ----------
    member : discord.Member
        The member gaining XP.
    guild : discord.Guild
        The guild where the member is gaining XP.
    """
    # Get blacklist status
    is_blacklisted = await self.db.levels.is_blacklisted(member.id, guild.id)
    if is_blacklisted:
        return

    last_message_time = await self.db.levels.get_last_message_time(member.id, guild.id)
    if last_message_time and self.is_on_cooldown(last_message_time):
        return

    current_xp, current_level = await self.db.levels.get_xp_and_level(member.id, guild.id)

    xp_increment = self.calculate_xp_increment(member)
    new_xp = current_xp + xp_increment
    new_level = self.calculate_level(new_xp)

    await self.db.levels.update_xp_and_level(
        member.id,
        guild.id,
        new_xp,
        new_level,
        datetime.datetime.fromtimestamp(time.time(), tz=datetime.UTC),
    )

    if new_level > current_level:
        logger.debug(f"User {member.name} leveled up from {current_level} to {new_level} in guild {guild.name}")
        await self.handle_level_up(member, guild, new_level)
is_on_cooldown(last_message_time: datetime.datetime) -> bool

Checks if the member is on cooldown.

Parameters:

Name Type Description Default
last_message_time datetime

The time of the last message.

required

Returns:

Type Description
bool

True if the member is on cooldown, False otherwise.

Source code in tux/cogs/services/levels.py
Python
def is_on_cooldown(self, last_message_time: datetime.datetime) -> bool:
    """
    Checks if the member is on cooldown.

    Parameters
    ----------
    last_message_time : datetime.datetime
        The time of the last message.

    Returns
    -------
    bool
        True if the member is on cooldown, False otherwise.
    """
    return (datetime.datetime.fromtimestamp(time.time(), tz=datetime.UTC) - last_message_time) < datetime.timedelta(
        seconds=self.xp_cooldown,
    )
handle_level_up(member: discord.Member, guild: discord.Guild, new_level: int) -> None async

Handles the level up process for a member.

Parameters:

Name Type Description Default
member Member

The member leveling up.

required
guild Guild

The guild where the member is leveling up.

required
new_level int

The new level of the member.

required
Source code in tux/cogs/services/levels.py
Python
async def handle_level_up(self, member: discord.Member, guild: discord.Guild, new_level: int) -> None:
    """
    Handles the level up process for a member.

    Parameters
    ----------
    member : discord.Member
        The member leveling up.
    guild : discord.Guild
        The guild where the member is leveling up.
    new_level : int
        The new level of the member.
    """
    await self.update_roles(member, guild, new_level)
update_roles(member: discord.Member, guild: discord.Guild, new_level: int) -> None async

Updates the roles of a member based on their new level.

Parameters:

Name Type Description Default
member Member

The member whose roles are being updated.

required
guild Guild

The guild where the member's roles are being updated.

required
new_level int

The new level of the member.

required
Source code in tux/cogs/services/levels.py
Python
async def update_roles(self, member: discord.Member, guild: discord.Guild, new_level: int) -> None:
    """
    Updates the roles of a member based on their new level.

    Parameters
    ----------
    member : discord.Member
        The member whose roles are being updated.
    guild : discord.Guild
        The guild where the member's roles are being updated.
    new_level : int
        The new level of the member.
    """
    roles_to_assign = [guild.get_role(rid) for lvl, rid in sorted(self.xp_roles.items()) if new_level >= lvl]
    highest_role = roles_to_assign[-1] if roles_to_assign else None

    if highest_role:
        await self.try_assign_role(member, highest_role)

    roles_to_remove = [r for r in member.roles if r.id in self.xp_roles.values() and r != highest_role]

    await member.remove_roles(*roles_to_remove)

    if highest_role or roles_to_remove:
        logger.debug(
            f"Updated roles for {member}: {f'Assigned {highest_role.name}' if highest_role else 'No role assigned'}{', Removed: ' + ', '.join(r.name for r in roles_to_remove) if roles_to_remove else ''}",
        )
try_assign_role(member: discord.Member, role: discord.Role) -> None async staticmethod

Tries to assign a role to a member.

Parameters:

Name Type Description Default
member Member

The member to assign the role to.

required
role Role

The role to assign.

required
Source code in tux/cogs/services/levels.py
Python
@staticmethod
async def try_assign_role(member: discord.Member, role: discord.Role) -> None:
    """
    Tries to assign a role to a member.

    Parameters
    ----------
    member : discord.Member
        The member to assign the role to.
    role : discord.Role
        The role to assign.
    """
    try:
        await member.add_roles(role)
    except Exception as error:
        logger.error(f"Failed to assign role {role.name} to {member}: {error}")
calculate_xp_for_level(level: int) -> float

Calculates the XP required for a given level.

Parameters:

Name Type Description Default
level int

The level to calculate XP for.

required

Returns:

Type Description
float

The XP required for the level.

Source code in tux/cogs/services/levels.py
Python
def calculate_xp_for_level(self, level: int) -> float:
    """
    Calculates the XP required for a given level.

    Parameters
    ----------
    level : int
        The level to calculate XP for.

    Returns
    -------
    float
        The XP required for the level.
    """
    return 500 * (level / 5) ** self.levels_exponent
calculate_xp_increment(member: discord.Member) -> float

Calculates the XP increment for a member.

Parameters:

Name Type Description Default
member Member

The member gaining XP.

required

Returns:

Type Description
float

The XP increment.

Source code in tux/cogs/services/levels.py
Python
def calculate_xp_increment(self, member: discord.Member) -> float:
    """
    Calculates the XP increment for a member.

    Parameters
    ----------
    member : discord.Member
        The member gaining XP.

    Returns
    -------
    float
        The XP increment.
    """
    return max((self.xp_multipliers.get(role.id, 1) for role in member.roles), default=1)
calculate_level(xp: float) -> int

Calculates the level based on XP.

Parameters:

Name Type Description Default
xp float

The XP amount.

required

Returns:

Type Description
int

The calculated level.

Source code in tux/cogs/services/levels.py
Python
def calculate_level(self, xp: float) -> int:
    """
    Calculates the level based on XP.

    Parameters
    ----------
    xp : float
        The XP amount.

    Returns
    -------
    int
        The calculated level.
    """
    return int((xp / 500) ** (1 / self.levels_exponent) * 5)
valid_xplevel_input(user_input: int) -> discord.Embed | None

Check if the input is valid.

Parameters:

Name Type Description Default
user_input int

The input to check.

required

Returns:

Type Description
Embed | None

A string if the input is valid, or a discord. Embed if there is an error.

Source code in tux/cogs/services/levels.py
Python
def valid_xplevel_input(self, user_input: int) -> discord.Embed | None:
    """
    Check if the input is valid.

    Parameters
    ----------
    user_input : int
        The input to check.

    Returns
    -------
    discord.Embed | None
        A string if the input is valid, or a discord. Embed if there is an error.
    """
    if user_input >= 2**63 - 1:
        embed: discord.Embed = EmbedCreator.create_embed(
            embed_type=EmbedCreator.ERROR,
            title="Error",
            description="Input must be less than the integer limit (2^63).",
        )
        return embed

    if user_input < 0:
        embed: discord.Embed = EmbedCreator.create_embed(
            embed_type=EmbedCreator.ERROR,
            title="Error",
            description="Input must be a positive integer.",
        )
        return embed

    return None
generate_progress_bar(current_value: int, target_value: int, bar_length: int = 10) -> str staticmethod

Generates an XP progress bar based on the current level and XP.

Parameters:

Name Type Description Default
current_value int

The current XP value.

required
target_value int

The target XP value.

required
bar_length int

The length of the progress bar. Defaults to 10.

10

Returns:

Type Description
str

The formatted progress bar.

Source code in tux/cogs/services/levels.py
Python
@staticmethod
def generate_progress_bar(
    current_value: int,
    target_value: int,
    bar_length: int = 10,
) -> str:
    """
    Generates an XP progress bar based on the current level and XP.

    Parameters
    ----------
    current_value : int
        The current XP value.
    target_value : int
        The target XP value.
    bar_length : int, optional
        The length of the progress bar. Defaults to 10.

    Returns
    -------
    str
        The formatted progress bar.
    """
    progress: float = current_value / target_value

    filled_length: int = int(bar_length * progress)
    empty_length: int = bar_length - filled_length

    bar: str = "▰" * filled_length + "▱" * empty_length

    return f"`{bar}` {current_value}/{target_value}"
get_level_progress(xp: float, level: int) -> tuple[int, int]

Get the progress towards the next level.

Parameters:

Name Type Description Default
xp float

The current XP.

required
level int

The current level.

required

Returns:

Type Description
tuple[int, int]

A tuple containing the XP progress within the current level and the XP required for the next level.

Source code in tux/cogs/services/levels.py
Python
def get_level_progress(self, xp: float, level: int) -> tuple[int, int]:
    """
    Get the progress towards the next level.

    Parameters
    ----------
    xp : float
        The current XP.
    level : int
        The current level.

    Returns
    -------
    tuple[int, int]
        A tuple containing the XP progress within the current level and the XP required for the next level.
    """
    current_level_xp = self.calculate_xp_for_level(level)
    next_level_xp = self.calculate_xp_for_level(level + 1)

    xp_progress = int(xp - current_level_xp)
    xp_required = int(next_level_xp - current_level_xp)

    return xp_progress, xp_required

Functions