Source code for discordSuperUtils.Leveling

from __future__ import annotations

import math
import time
from typing import (
    Iterable,
    TYPE_CHECKING,
    List
)

from .Base import DatabaseChecker

if TYPE_CHECKING:
    import discord


[docs]class LevelingAccount: def __init__(self, leveling_manager: LevelingManager, member: discord.Member): self.leveling_manager = leveling_manager self.table = self.leveling_manager.tables["xp"] self.member = member def __str__(self): return f"<Account MEMBER={self.member}, GUILD={self.member.guild}>" @property def __checks(self): return LevelingManager.generate_checks(self.member)
[docs] async def xp(self): xp_data = await self.leveling_manager.database.select(self.table, ['xp'], self.__checks) return xp_data["xp"]
[docs] async def level(self): rank_data = await self.leveling_manager.database.select(self.table, ['rank'], self.__checks) return rank_data["rank"]
[docs] async def next_level(self): level_up_data = await self.leveling_manager.database.select(self.table, ['level_up'], self.__checks) return level_up_data["level_up"]
[docs] async def percentage_next_level(self): level_up = await self.next_level() xp = await self.xp() initial_xp = await self.initial_rank_xp() return min(abs(math.floor(abs(xp - initial_xp) / (level_up - initial_xp) * 100)), 100)
[docs] async def initial_rank_xp(self): next_level = await self.next_level() return 0 if next_level == 50 else next_level / self.leveling_manager.rank_multiplier
[docs] async def set_xp(self, value): await self.leveling_manager.database.update(self.table, {"xp": value}, self.__checks)
[docs] async def set_level(self, value): await self.leveling_manager.database.update(self.table, {"rank": value}, self.__checks)
[docs] async def set_next_level(self, value): await self.leveling_manager.database.update(self.table, {"level_up": value}, self.__checks)
[docs]class LevelingManager(DatabaseChecker): def __init__(self, bot, award_role: bool = False, default_role_interval: int = 5, xp_on_message=5, rank_multiplier=1.5, xp_cooldown=60): super().__init__([ {'guild': 'snowflake', 'member': 'snowflake', 'rank': 'number', 'xp': 'number', 'level_up': 'number'}, {'guild': 'snowflake', 'interval': 'smallnumber'}, {'guild': 'snowflake', 'role': 'snowflake'} ], ['xp', 'roles', 'role_list']) self.bot = bot self.award_role = award_role self.default_role_interval = default_role_interval self.xp_on_message = xp_on_message self.rank_multiplier = rank_multiplier self.xp_cooldown = xp_cooldown self.cooldown_members = {} self.add_event(self.on_database_connect)
[docs] async def set_interval(self, guild: discord.Guild, interval: int = None) -> None: """ Set the role interval of a guild. :param interval: The interval to set. :type interval: int :param guild: The guild to set the role interval in. :type guild: discord.Guild :return: :rtype: None """ interval = interval if interval is not None else self.default_role_interval if 0 >= interval: raise ValueError("The interval must be greater than 0.") sql_insert_data = { 'guild': guild.id, 'interval': interval } await self.database.updateorinsert(self.tables["roles"], sql_insert_data, {'guild': guild.id}, sql_insert_data)
[docs] async def get_roles(self, guild: discord.Guild) -> List[int]: """ Returns the role IDs of the guild. :param guild: The guild to get the roles from. :type guild: discord.Guild :return: :rtype: List[int] """ self._check_database() return [role["role"] for role in await self.database.select(self.tables["role_list"], ['role'], {'guild': guild.id}, fetchall=True)]
[docs] async def set_roles(self, guild: discord.Guild, roles: Iterable[discord.Role]) -> None: """ Sets the roles of the guild. :param guild: The guild to set the roles in. :type guild: discord.Guild :param roles: The roles to set. :type roles: Iterable[discord.Role] :return: :rtype: None """ self._check_database() await self.database.delete(self.tables["role_list"], {'guild': guild.id}) for role in roles: await self.database.insert(self.tables["role_list"], {'guild': guild.id, "role": role.id})
[docs] async def on_database_connect(self): self.bot.add_listener(self.__handle_experience, 'on_message')
[docs] @staticmethod def generate_checks(member: discord.Member): return {'guild': member.guild.id, 'member': member.id}
async def __handle_experience(self, message): self._check_database() if not message.guild or message.author.bot: return member_cooldown = self.cooldown_members.setdefault(message.guild.id, {}).get(message.author.id, 0) if (time.time() - member_cooldown) >= self.xp_cooldown: await self.create_account(message.author) member_account = await self.get_account(message.author) await member_account.set_xp(await member_account.xp() + self.xp_on_message) self.cooldown_members[message.guild.id][message.author.id] = time.time() leveled_up = False while await member_account.xp() >= await member_account.next_level(): await member_account.set_next_level(await member_account.next_level() * self.rank_multiplier) await member_account.set_level(await member_account.level() + 1) leveled_up = True if leveled_up: roles = [] if self.award_role: role_ids = await self.get_roles(message.guild) interval = await self.database.select(self.tables['roles'], ['interval'], {'guild': message.guild.id}) interval = interval["interval"] if interval else self.default_role_interval if role_ids: member_level = await member_account.level() if member_level % interval == 0 and member_level // interval <= len(role_ids): roles = [message.guild.get_role(role_id) for role_id in role_ids][:await member_account.level() // interval] roles.reverse() roles = [role for role in roles if role] await self.call_event('on_level_up', message, member_account, roles) if roles: await message.author.add_roles(*roles)
[docs] async def create_account(self, member): self._check_database() await self.database.insertifnotexists(self.tables["xp"], dict( zip( self.tables_column_data[0], [member.guild.id, member.id, 1, 0, 50] ) ), self.generate_checks(member))
[docs] async def get_account(self, member): self._check_database() member_data = await self.database.select(self.tables["xp"], [], self.generate_checks(member), True) if member_data: return LevelingAccount(self, member) return None
[docs] async def get_leaderboard(self, guild: discord.Guild): self._check_database() guild_info = sorted( await self.database.select(self.tables['xp'], [], {'guild': guild.id}, True), key=lambda x: x["xp"], reverse=True ) members = [] for member_info in guild_info: member = guild.get_member(member_info['member']) if member: members.append(LevelingAccount(self, member)) return members