Giter VIP home page Giter VIP logo

discordlevelingsystem's Introduction

A library to implement a leveling system into a discord bot. One of the most popular discord bots out there is MEE6 and it's leveling system. This library provides ways to easily implement one for yourself. It uses SQLite (aiosqlite) to locally query things such as their XP, rank, and level. Various amounts of other methods and classes are also provided so you can access or remove contents from the database file.

Downloads Downloads python_version

Installing

You can install the latest PyPI version of the library by doing:

$ pip install discordLevelingSystem

Or the development version:

$ pip install git+https://github.com/Defxult/discordLevelingSystem


Showcase

showcase


How to import

from discordLevelingSystem import DiscordLevelingSystem, LevelUpAnnouncement, RoleAward

Intents

Intents are required for proper functionality

bot = commands.Bot(..., intents=discord.Intents(messages=True, guilds=True, members=True))

DiscordLevelingSystem

class DiscordLevelingSystem(rate=1, per=60.0, awards=None, **kwargs)


Parameters of the DiscordLevelingSystem constructor

  • rate (int) The amount of messages each member can send before the cooldown triggers
  • per (float) The amount of seconds each member has to wait before gaining more XP, aka the cooldown
  • awards (Optional[Dict[int, List[RoleAward]]]) The role given to a member when they reach a RoleAward level requirement

Kwargs of the DiscordLevelingSystem constructor

Name Type Default Value Info
no_xp_roles Sequence[int] None A sequence of role ID's. Any member with any of those roles will not gain XP when sending messages
no_xp_channels Sequence[int] None A sequence of text channel ID's. Any member sending messages in any of those text channels will not gain XP
announce_level_up bool True If True, level up messages will be sent when a member levels up
stack_awards bool True If this is True, when the member levels up the assigned role award will be applied. If False, the previous role award will be removed and the level up assigned role will also be applied
level_up_announcement Union[LevelUpAnnouncement, Sequence[LevelUpAnnouncement]] LevelUpAnnouncement() The message that is sent when someone levels up. If this is a sequence of LevelUpAnnouncement, one is selected at random
bot Union[AutoShardedBot, Bot] None Your bot instance variable. Used only if you'd like to use the on_dls_level_up event

Attributes

  • no_xp_roles
  • no_xp_channels
  • announce_level_up
  • stack_awards
  • level_up_announcement
  • bot
  • rate (int) Read only property from the constructor
  • per (float) Read only property from the constructor
  • database_file_path (str) Read only property
  • active (bool) Enable/disable the leveling system. If False, nobody can gain XP when sending messages unless this is set back to True

NOTE: All attributes can be set during initialization


Initial Setup

When setting up the leveling system, a database file needs to be created in order for the library to function.

  • Associated static method
    • DiscordLevelingSystem.create_database_file(path: str)

The above static method is used to create the database file for you in the path you specify. This method only needs to be called once. Example:

DiscordLevelingSystem.create_database_file(r'C:\Users\Defxult\Documents')

Once created, there is no need to ever run that method again unless you want to create a new database file from scratch. Now that you have the database file, you can use the leveling system.


Connecting to the Database

  • Associated method
    • DiscordLevelingSystem.connect_to_database_file(path: str)

Since the database file has already been created, all you need to do is connect to it.

NOTE: When connecting to the database file, the event loop must not be running

EXAMPLE
from discord.ext import commands
from discordLevelingSystem import DiscordLevelingSystem

bot = commands.Bot(...)
lvl = DiscordLevelingSystem()
lvl.connect_to_database_file(r'C:\Users\Defxult\Documents\DiscordLevelingSystem.db')

bot.run(...)

RoleAward

class RoleAward(role_id: int, level_requirement: int, role_name=None)

You can assign roles to the system so when someone levels up to a certain level, they are given that role. RoleAward is how that is accomplished.


Parameters of the RoleAward constructor

  • role_id (int) ID of the role that is to be awarded.
  • level_requirement (int) What level is required for a member to be awarded the role.
  • role_name (Optional[str]) A name you can set for the award. Nothing is done with this value, it is used for visual identification purposes only.

Attributes

  • role_id
  • level_requirement
  • role_name
  • mention (str) The discord role mention string

When creating role awards, all role IDs and level requirements must be unique. Level requirements must also be in ascending order. It is also possible to assign different role awards for different guilds. If you don't want any role awards, set the awards parameter to None. When setting awards, it accepts a dict where the keys are guild IDs and the values are a list of RoleAward

EXAMPLE
from discordLevelingSystem import DiscordLevelingSystem, RoleAward

johns_server = 587937522043060224
janes_server = 850809412011950121

my_awards = {
    johns_server : [
        RoleAward(role_id=831672678586777601, level_requirement=1, role_name='Rookie'),
        RoleAward(role_id=831672730583171073, level_requirement=2, role_name='Associate'),
        RoleAward(role_id=831672814419050526, level_requirement=3, role_name='Legend')
    ],
    janes_server : [
        RoleAward(role_id=851400453904400385, level_requirement=1, role_name='Silver'),
        RoleAward(role_id=851379776111116329, level_requirement=2, role_name='Gold'),
        RoleAward(role_id=851959077071880202, level_requirement=3, role_name='Diamond')
    ]
}

lvl = DiscordLevelingSystem(..., awards=my_awards)

LevelUpAnnouncement

class LevelUpAnnouncement(message=default_message, level_up_channel_ids=None, allowed_mentions=default_mentions, tts=False, delete_after=None)

Level up announcements are for when you want to implement your own level up messages. It provides access to who leveled up, their rank, level and much more. It also uses some of discord.py's kwargs from it's Messageable.send such as allowed_mentions, tts, and delete_after to give you more control over the sent message.


Parameters of the LevelUpAnnouncement constructor

  • message (Union[str, discord.Embed]) The message that is sent when someone levels up. Defaults to "<mention>, you are now **level <level>!**"

  • level_up_channel_ids (Optional[Sequence[int]]) The text channel IDs where all level up messages will be sent for each server. If None, the level up message will be sent in the channel where they sent the message (example below).

  • allowed_mentions (discord.AllowedMentions) Used to determine who can be pinged in the level up message. Defaults to discord.AllowedMentions(everyone=False, users=True, roles=False, replied_user=False)

  • tts (bool) When the level up message is sent, have discord read the level up message aloud.

  • delete_after (Optional[float]) Delete the level up message after an x amount of seconds.

Class Attributes

The LevelUpAnnouncement class provides a set of markdown attributes for you to use so you can access certain information in a level up message.

  • LevelUpAnnouncement.TOTAL_XP The members current total XP amount
  • LevelUpAnnouncement.LEVEL The members current level
  • LevelUpAnnouncement.RANK The members current rank

The below markdown attributes takes the information from a discord.Member object so you can access member information in the level up message.

  • LevelUpAnnouncement.Member.avatar_url
  • LevelUpAnnouncement.Member.banner_url
  • LevelUpAnnouncement.Member.created_at
  • LevelUpAnnouncement.Member.default_avatar_url
  • LevelUpAnnouncement.Member.discriminator
  • LevelUpAnnouncement.Member.display_avatar_url
  • LevelUpAnnouncement.Member.display_name
  • LevelUpAnnouncement.Member.id
  • LevelUpAnnouncement.Member.joined_at
  • LevelUpAnnouncement.Member.mention
  • LevelUpAnnouncement.Member.name
  • LevelUpAnnouncement.Member.nick
  • LevelUpAnnouncement.Member.Guild.icon_url
  • LevelUpAnnouncement.Member.Guild.id
  • LevelUpAnnouncement.Member.Guild.name
EXAMPLE
from discordLevelingSystem import DiscordLevelingSystem, LevelUpAnnouncement

embed = discord.Embed()
embed.set_author(name=LevelUpAnnouncement.Member.name, icon_url=LevelUpAnnouncement.Member.avatar_url)
embed.description = f'Congrats {LevelUpAnnouncement.Member.mention}! You are now level {LevelUpAnnouncement.LEVEL} ๐Ÿ˜Ž'

announcement = LevelUpAnnouncement(embed)

lvl = DiscordLevelingSystem(..., level_up_announcement=announcement)

# NOTE: You can have multiple level up announcements by setting the parameter to a sequence of LevelUpAnnouncement
lvl = DiscordLevelingSystem(..., level_up_announcement=[announcement_1, announcement_2, ...])

When it comes to level_up_channel_ids, you can set a designated channel for each server. If you don't set a level up channel ID for a specific server, the level up message will be sent in the channel where the member leveled up. You don't have to specify a level up channel ID for each server unless you'd like to.

johns_bot_commands = 489374746737648734 # text channel ID from server A
janes_levelup_channel = 58498304930493094 # text channel ID from server B

announcement = LevelUpAnnouncement(..., level_up_channel_ids=[johns_bot_commands, janes_levelup_channel])
lvl = DiscordLevelingSystem(..., level_up_announcement=announcement)

Handling XP

Method award_xp is how members gain XP. This method is placed inside the on_message event of your bot. Members will gain XP if they send a message and if they're not on cooldown. Spamming messages will not give them XP.

NOTE: Members cannot gain XP in DM's

  • Associated methods
    • await DiscordLevelingSystem.add_xp(member: Member, amount: int)
    • await DiscordLevelingSystem.remove_xp(member: Member, amount: int)
    • await DiscordLevelingSystem.set_level(member: Member, level: int)
    • await DiscordLevelingSystem.award_xp(*, amount=[15, 25], message: Message, refresh_name=True, **kwargs)

Parameters for award_xp

  • amount (Union[int, Sequence[int]]) The amount of XP to award to the member per message. Must be from 1-25. Can be a sequence with a minimum and maximum length of two. If amount is a sequence of two integers, it will randomly pick a number in between those numbers including the numbers provided.
  • message (discord.Message) A discord message object
  • refresh_name (bool) Everytime the member sends a message, check if their name still matches the name in the database. If it doesn't match, update the database to match their current name. It is suggested to leave this as True so the database can always have the most up-to-date record.

Kwargs for award_xp

  • bonus (DiscordLevelingSystem.Bonus) Used to set the roles that will be awarded bonus XP.
    • class Bonus(role_ids: Sequence[int], bonus_amount: int, multiply: bool)
    • Parameters of the DiscordLevelingSystem.Bonus constructor
      • role_ids (Sequence[int]) The role(s) a member must have to be able to get bonus XP. They only need to have one of these roles to get the bonus
      • bonus_amount (int) Amount of extra XP to be awarded
      • multiply (bool) If set to True, this will operate on a x2, x3 basis. Meaning if you have the awarded XP amount set to 10 and you want the bonus XP role to be awarded 20, it must be set to 2, not 10. If False, it operates purely on the given value. Meaning if you have the awarded XP set to 10 and you want the bonus XP role to be awarded 20, it must be set to 10.
EXAMPLE
lvl = DiscordLevelingSystem(...)

nitro_booster = 851379776111116329
associate_role = 851400453904400385

@bot.event
async def on_message(message):
    await lvl.award_xp(amount=[15, 25], message=message, bonus=DiscordLevelingSystem.Bonus([nitro_booster, associate_role], 20, multiply=False))

MemberData

Accessing the raw information inside the database file can look a bit messy if you don't know exactly what you're looking at. To make things easier, this library comes with the MemberData class. A class which returns information about a specific member in the database.

  • Associated methods
    • await DiscordLevelingSystem.get_data_for(member: Member) -> MemberData
    • await DiscordLevelingSystem.each_member_data(guild: Guild, sort_by=None, limit=None) -> List[MemberData]

Attributes

  • id_number (int) The members ID
  • name (str) The members name
  • level (int) The members level
  • xp (int) The members xp
  • total_xp (int) The members total xp
  • rank (Optional[int]) The members rank
  • mention (str) The discord member mention string

Methods

  • MemberData.to_dict() -> dict
EXAMPLE
lvl = DiscordLevelingSystem(...)

@bot.command()
async def rank(ctx):
    data = await lvl.get_data_for(ctx.author)
    await ctx.send(f'You are level {data.level} and your rank is {data.rank}')

@bot.command()
async def leaderboard(ctx):
    data = await lvl.each_member_data(ctx.guild, sort_by='rank')
    # show the leaderboard whichever way you'd like

Events

You can set an event to be called when a member levels up. Using the event is considered as an enhanced LevelUpAnnouncement because it provides more capabilities rather than simply sending a message with only text/an embed. The on_dls_level_up event takes three parameters:

  • member (discord.Member) The member that leveled up
  • message (discord.Message) The message that triggered the level up
  • data (MemberData) The database information for that member
bot = commands.Bot(...)
lvl = DiscordLevelingSystem(..., bot=bot) # your bot instance variable is needed

@bot.event
async def on_dls_level_up(member: discord.Member, message: discord.Message, data: MemberData):
    # You can do a lot more here compared to LevelUpAnnouncement
    # - create a level up image and send it with discord.File
    # - call additional functions that you may need
    # - access to all attributes/methods that are available within discord.Member and discord.Message

NOTE: LevelUpAnnouncement and on_dls_level_up are not the same. Level up messages are sent by default by the library. If you'd like to only use on_dls_level_up, you need to disable level up announcements (lvl.announce_level_up = False)


Full Example

With all classes and core methods introduced, here is a basic implementation of this library.

from discord.ext import commands
from discordLevelingSystem import DiscordLevelingSystem, RoleAward, LevelUpAnnouncement

bot = commands.Bot(...)

main_guild_id = 850809412011950121

my_awards = {
    main_guild_id : [
        RoleAward(role_id=831672678586777601, level_requirement=1, role_name='Rookie'),
        RoleAward(role_id=831672730583171073, level_requirement=2, role_name='Associate'),
        RoleAward(role_id=831672814419050526, level_requirement=3, role_name='Legend')
    ]
}

announcement = LevelUpAnnouncement(f'{LevelUpAnnouncement.Member.mention} just leveled up to level {LevelUpAnnouncement.LEVEL} ๐Ÿ˜Ž')

# DiscordLevelingSystem.create_database_file(r'C:\Users\Defxult\Documents') database file already created
lvl = DiscordLevelingSystem(awards=my_awards, level_up_announcement=announcement)
lvl.connect_to_database_file(r'C:\Users\Defxult\Documents\DiscordLevelingSystem.db')

@bot.event
async def on_message(message):
    await lvl.award_xp(amount=15, message=message)

bot.run(...)

All methods for DiscordLevelingSystem

Click to show all methods
  • await add_record(guild_id, member_id, member_name, level) - Manually add a record to the database. If the record already exists (the guild_id and member_id was found), only the level will be updated. If there were no records that matched those values, all provided information will be added

    • Parameters
      • guild_id (int) The guild ID to register
      • member_id (int) The member ID to register
      • member_name (str) The member name to register
      • level (int) The member level to register. Must be from 0-100
    • Raises
      • DiscordLevelingSystemError - The value given from a parameter was not of the correct type or "level" was not 0-100
  • await add_xp(member, amount) - Give XP to a member. This also changes their level so it matches the associated XP

    • Parameters
      • member (discord.Member) The member to give XP to
      • amount (int) Amount of XP to give to the member
    • Raises
      • DatabaseFileNotFound - The database file was not found
      • LeaderboardNotFound - Table "leaderboard" in the database file is missing
      • ImproperLeaderboard - Leaderboard table was altered. Components changed or deleted
      • NotConnected - Attempted to use a method that requires a connection to a database file
      • DiscordLevelingSystemError - Parameter "amount" was less than or equal to zero. The minimum value is 1
  • await award_xp(*, amount = [15, 25], message, refresh_name = True, **kwargs) - Give XP to the member that sent a message

    • Parameters
      • amount (Union[int, Sequence[int]])
      • message (discord.Message) A message object
      • refresh_name (bool) Everytime the member sends a message, check if their name still matches the name in the database. If it doesn't match, update the database to match their current name. It is suggested to leave this as True so the database can always have the most up-to-date record
    • Kwargs
      • bonus (DiscordLevelingSystem.Bonus) Set the bonus values. Read the DiscordLevelingSystem.Bonus doc string for more details (defaults to None)
    • Raises
      • DatabaseFileNotFound - The database file was not found
      • LeaderboardNotFound - Table "leaderboard" in the database file is missing
      • ImproperLeaderboard - Leaderboard table was altered. Components changed or deleted
      • NotConnected - Attempted to use a method that requires a connection to a database file
  • backup_database_file(path, with_timestamp = False) - Create a copy of the database file to the specified path. If a copy of the backup file is already in the specified path it will be overwritten

    • Parameters
      • path (str) The path to copy the database file to
      • with_timestamp (bool) Creates a unique file name that has the date and time of when the backup file was created. This is useful when you want multiple backup files
    • Raises
      • DiscordLevelingSystemError - Path doesn't exist or points to another file
      • NotConnected - Attempted to use a method that requires a connection to a database file
  • await change_cooldown(rate, per) - Update the cooldown rate

    • Parameters
      • rate (int) The amount of messages each member can send before the cooldown triggers
      • per (float) The amount of seconds each member has to wait before gaining more XP, aka the cooldown
    • Raises
      • DatabaseFileNotFound - The database file was not found
      • LeaderboardNotFound - Table "leaderboard" in the database file is missing
      • ImproperLeaderboard - Leaderboard table was altered. Components changed or deleted
      • NotConnected - Attempted to use a method that requires a connection to a database file
      • DiscordLevelingSystemError - The rate or per value was not greater than zero
  • await clean_database(guild) - Removes the data for members that are no longer in the guild, thus reducing the database file size. It is recommended to have this method in a background loop in order to keep the database file free of records that are no longer in use

    • Parameters
      • guild (discord.Guild) The guild records to clean
    • Returns
      • (Optional[int]) The amount of records that were removed from the database
    • Raises
      • DatabaseFileNotFound - The database file was not found
      • LeaderboardNotFound - Table "leaderboard" in the database file is missing
      • ImproperLeaderboard - Leaderboard table was altered. Components changed or deleted
      • NotConnected - Attempted to use a method that requires a connection to a database file
  • connect_to_database_file(path) - Connect to the existing database file in the specified path

    • Parameters
      • path (str) The location of the database file
    • Raises
      • ConnectionFailure - Attempted to connect to the database file when the event loop is already running
      • DatabaseFileNotFound - The database file was not found
  • static method create_database_file(path = None) - Create the database file and implement the SQL data for the database

    • Parameters
      • path (Optional[str]) The location to create the database file. If None, the file is created in the current working directory
    • Raises
      • ConnectionFailure - Attempted to create the database file when the event loop is already running
      • DiscordLevelingSystemError - The path does not exist or the path points to a file instead of a directory
  • await each_member_data(guild, sort_by = None, limit = None) - Return each member in the database as a MemberData object for easy access to their XP, level, etc. You can sort the data with sort_by with the below values

    • Parameters
      • guild (discord.Guild) A guild object
      • sort_by (Optional[str]) Return each member sorted by: "name", "level", "xp", "rank". If None, it will return in the order they were added to the database
      • limit (Optional[int]) Restrict the amount of records returned to the specified amount
    • Returns
      • List[MemberData]
    • Raises
      • DatabaseFileNotFound - The database file was not found
      • LeaderboardNotFound - Table "leaderboard" in the database file is missing
      • ImproperLeaderboard - Leaderboard table was altered. Components changed or deleted
      • NotConnected - Attempted to use a method that requires a connection to a database file
      • DiscordLevelingSystemError - The value of sort_by was not recognized or guild was not of type discord.Guild
  • await export_as_json(path, guild) - Export a json file that represents the database to the path specified

    • Parameters
      • path (str) Path to copy the json file to
      • guild (discord.Guild) The guild for which the data should be extracted from. If None, all guild information will be extracted from the database
    • Raises
      • DatabaseFileNotFound - The database file was not found
      • LeaderboardNotFound - Table "leaderboard" in the database file is missing
      • ImproperLeaderboard - Leaderboard table was altered. Components changed or deleted
      • NotConnected - Attempted to use a method that requires a connection to a database file
      • DiscordLevelingSystemError - The path does not exist or does not point to a directory
  • get_awards(guild = None) - Get all RoleAward's or only the RoleAward's assigned to the specified guild

    • Parameters
      • guild (Optional[Union[discord.Guild, int]]) A guild object or a guild ID
    • Returns
      • (Union[Dict[int, List[RoleAward]], List[RoleAward]]) If guild is None, this return the awards dict that was set in constructor. If guild is specified, it returns a List[RoleAward] that matches the specified guild ID. Can also return None if awards were never set or if the awards for the specified guild was not found
  • await get_data_for(member) - Get the MemberData object that represents the specified member

    • Parameters
      • member (discord.Member) The member to get the data for
    • Returns
      • (MemberData) Can be None if the member isn't in the database
    • Raises
      • DatabaseFileNotFound - The database file was not found
      • LeaderboardNotFound - Table "leaderboard" in the database file is missing
      • ImproperLeaderboard - Leaderboard table was altered. Components changed or deleted
      • NotConnected - Attempted to use a method that requires a connection to a database file
  • await get_level_for(member) - Get the level for the specified member

    • Parameters
      • member (discord.Member) Member to get the level for
    • Returns
      • (int) Can be None if the member isn't in the database
    • Raises
      • DatabaseFileNotFound - The database file was not found
      • LeaderboardNotFound - Table "leaderboard" in the database file is missing
      • ImproperLeaderboard - Leaderboard table was altered. Components changed or deleted
      • NotConnected - Attempted to use a method that requires a connection to a database file
  • await get_rank_for(member) - Get the rank for the specified member

    • Parameters
      • member (discord.Member) Member to get the rank for
    • Returns
      • (int) Can be None if the member isn't ranked yet
    • Raises
      • DatabaseFileNotFound - The database file was not found
      • LeaderboardNotFound - Table "leaderboard" in the database file is missing
      • ImproperLeaderboard - Leaderboard table was altered. Components changed or deleted
      • NotConnected - Attempted to use a method that requires a connection to a database file
  • await get_record_count(guild = None) - Get the amount of members that are registered in the database. If guild is set to None, ALL members in the database will be counted

    • Parameters
      • guild (Optional[discord.Guild]) The guild for which to count the amount of records
    • Returns
      • (int)
    • Raises
      • DatabaseFileNotFound - The database file was not found
      • LeaderboardNotFound - Table "leaderboard" in the database file is missing
      • ImproperLeaderboard - Leaderboard table was altered. Components changed or deleted
      • NotConnected - Attempted to use a method that requires a connection to a database file
  • await get_total_xp_for(member) - Get the total XP for the specified member

    • Parameters
      • member (discord.Member) Member to get the total XP for
    • Returns
      • (int) Can be None if the member isn't in the database
    • Raises
      • DatabaseFileNotFound - The database file was not found
      • LeaderboardNotFound - Table "leaderboard" in the database file is missing
      • ImproperLeaderboard - Leaderboard table was altered. Components changed or deleted
      • NotConnected - Attempted to use a method that requires a connection to a database file
  • await get_xp_for(member) - Get the XP for the specified member

    • Parameters
      • member (discord.Member) Member to get the XP for
    • Returns
      • (int) Can be None if the member isn't in the database
    • Raises
      • DatabaseFileNotFound - The database file was not found
      • LeaderboardNotFound - Table "leaderboard" in the database file is missing
      • ImproperLeaderboard - Leaderboard table was altered. Components changed or deleted
      • NotConnected - Attempted to use a method that requires a connection to a database file
  • static method get_xp_for_level(level) - Returns the total amount of XP needed for the specified level. Levels go from 0-100

    • Parameters
      • level (int) The level XP information to retrieve
    • Returns
      • (int)
    • Raises
      • DiscordLevelingSystemError - The level specified does not exist
  • await insert(bot, guild_id, users, using, overwrite = False, show_results = True) - Insert the records from your own leveling system into the library. A lot of leveling system tutorials out there use json files to store information. Although it might work, it is insufficient because json files are not made to act as a database. Using an actual database file has many benefits over a json file

    • Parameters
      • bot (Union[discord.ext.commands.Bot, discord.ext.commands.AutoShardedBot]) Your bot instance variable
      • guild_id (int) ID of the guild that you used your leveling system with
      • users (Dict[int, int]) This is the information that will be added to the database. The keys are user ID's, and the values are the users total XP or level. Note: This library only uses levels 0-100 and XP 0-1899250. If any number in this dict are over the levels/XP threshold, it is implicitly set back to this libraries maximum value
      • using (str) What structure your leveling system used. Options: "xp" or "levels". Some leveling systems give users only XP and they are ranked up based on that XP value. Others use a combination of levels and XP. If all the values in the users dict are based on XP, set this to "xp". If they are based on a users level, set this to "levels"
      • overwrite (bool) If a user you've specified in the users dict already has a record in the database, overwrite their current record with the one your inserting
      • show_results (bool) Print the results for how many of the users were successfully added to the database file. If any are unsuccessful, their ID along with the value you provided will also be shown
    • Raises
      • DiscordLevelingSystemError - The value given from a parameter was not of the correct type. The users dict was empty. Or your bot is not in the guild associated with guild_id
  • await is_in_database(member, guild = None) - A quick check to see if a member is in the database. This is not guild specific although it can be if guild is specified

    • Parameters
      • member (Union[discord.Member, int]) The member to check for. Can be the member object or that members ID
      • guild (Optional[discord.Guild]) The guild to check if the member is registered in
    • Returns
      • (bool)
    • Raises
      • DatabaseFileNotFound - The database file was not found
      • LeaderboardNotFound - Table "leaderboard" in the database file is missing
      • ImproperLeaderboard - Leaderboard table was altered. Components changed or deleted
      • NotConnected - Attempted to use a method that requires a connection to a database file
      • DiscordLevelingSystemError - Parameter member was not of type discord.Member or int
  • static method levels_and_xp( ) - Get the raw dict representation for the amount of levels/XP in the system. The keys in the dict returned is each level, and the values are the amount of XP needed to be awarded that level

    • Returns
      • (Dict[str, int])
  • await next_level(member) - Get the next level for the specified member

    • Parameters
      • member (discord.Member) Member to get the next level for
    • Returns
      • (int) If the member is currently max level (100), it will return 100. This can also return None if the member is not in the database
    • Raises
      • DatabaseFileNotFound - The database file was not found
      • LeaderboardNotFound - Table "leaderboard" in the database file is missing
      • ImproperLeaderboard - Leaderboard table was altered. Components changed or deleted
      • NotConnected - Attempted to use a method that requires a connection to a database file
  • await next_level_up(member) - Get the amount of XP needed for the specified member to level up

    • Parameters
      • member (discord.Member) Member to get the amount of XP needed for a level up
    • Returns
      • (int) Returns 0 if the member is currently at max level. Can return None if the member is not in the database.
    • Raises
      • DatabaseFileNotFound - The database file was not found
      • LeaderboardNotFound - Table "leaderboard" in the database file is missing
      • ImproperLeaderboard - Leaderboard table was altered. Components changed or deleted
      • NotConnected - Attempted to use a method that requires a connection to a database file
  • await raw_database_contents(guild = None) - Returns everything in the database. Can specify which guild information will be extracted

    • Parameters
      • guild (Optional[discord.Guild]) The guild to extract the raw database contents from. If None, information about all guilds will be extracted
    • Returns
      • List[Tuple[int, int, str, int, int, int]] The tuples inside the list represents each row of the database:
        • Index 0 is the guild ID
        • Index 1 is their ID
        • Index 2 is their name
        • Index 3 is their level
        • Index 4 is their XP
        • Index 5 is their total xp
        • Can be an empty list if nothing is in the database
    • Raises
      • DatabaseFileNotFound - The database file was not found
      • LeaderboardNotFound - Table "leaderboard" in the database file is missing
      • ImproperLeaderboard - Leaderboard table was altered. Components changed or deleted
      • NotConnected - Attempted to use a method that requires a connection to a database file
  • await refresh_names(guild) - Update names inside the database. This does not add anything new. It simply verifies if the name in the database matches their current name, and if they don't match, update the database name

    • Parameters
      • guild (discord.Guild) A guild object
    • Returns
      • (Optional[int]) The amount of records in the database that were updated
    • Raises
      • DatabaseFileNotFound - The database file was not found
      • LeaderboardNotFound - Table "leaderboard" in the database file is missing
      • ImproperLeaderboard - Leaderboard table was altered. Components changed or deleted
      • NotConnected - Attempted to use a method that requires a connection to a database file
  • await remove_from_database(member, guild = None) - Remove a member from the database. This is not guild specific although it can be if guild is specified

    • Parameters
      • member (Union[discord.Member, int]) The member to remove. Can be the member object or that members ID
      • guild (Optional[discord.Guild]) If this parameter is given, it will remove the record of the specified member only from the specified guild record. If None, it will remove all records no matter the guild
    • Returns
      • (Optional[bool]) Returns True if the member was successfully removed from the database. False if the member was not in the database so there was nothing to remove
    • Raises
      • DatabaseFileNotFound - The database file was not found
      • LeaderboardNotFound - Table "leaderboard" in the database file is missing
      • ImproperLeaderboard - Leaderboard table was altered. Components changed or deleted
      • DiscordLevelingSystemError - Parameter member was not of type discord.Member or int
      • NotConnected - Attempted to use a method that requires a connection to a database file
  • await remove_xp(member, amount) - Remove XP from a member. This also changes their level so it matches the associated XP

    • Parameters
      • member (discord.Member) The member to remove XP from
      • amount (int) Amount of XP to remove from the member
    • Raises
      • DatabaseFileNotFound - The database file was not found
      • LeaderboardNotFound - Table "leaderboard" in the database file is missing
      • ImproperLeaderboard - Leaderboard table was altered. Components changed or deleted
      • NotConnected - Attempted to use a method that requires a connection to a database file
      • DiscordLevelingSystemError - Parameter "amount" was less than or equal to zero. The minimum value is 1
  • await reset_everyone(guild, *, intentional = False) - Sets EVERYONES XP, total XP, and level to zero in the database. Can specify which guild to reset

    • Parameters
      • guild (Union[discord.Guild, None]) The guild for which everyone will be reset. If this is set to None, everyone in the entire database will be reset
      • intentional (bool) A simple kwarg to try and ensure that this action is indeed what you want to do. Once executed, this cannot be undone
    • Raises
      • DatabaseFileNotFound - The database file was not found
      • LeaderboardNotFound - Table "leaderboard" in the database file is missing
      • ImproperLeaderboard - Leaderboard table was altered. Components changed or deleted
      • NotConnected - Attempted to use a method that requires a connection to a database file
      • FailSafe - "intentional" argument for this method was set to False in case you called this method by mistake
  • await reset_member(member) - Sets the members XP, total XP, and level to zero

    • Parameters
      • member (discord.Member) The member to reset
    • Raises
      • DatabaseFileNotFound The database file was not found
      • LeaderboardNotFound Table "leaderboard" in the database file is missing
      • ImproperLeaderboard Leaderboard table was altered. Components changed or deleted
      • NotConnected Attempted to use a method that requires a connection to a database file
  • await set_level(member, level) - Sets the level for the member. This also changes their total XP so it matches the associated level

    • Parameters
      • member (discord.Member) The member who's level will be set
      • level (int) Level to set. Must be from 0-100
    • Raises
      • DatabaseFileNotFound - The database file was not found
      • LeaderboardNotFound - Table "leaderboard" in the database file is missing
      • ImproperLeaderboard - Leaderboard table was altered. Components changed or deleted
      • NotConnected - Attempted to use a method that requires a connection to a database file
      • DiscordLevelingSystemError - Parameter "level" was not from 0-100
  • await sql_query_get(sql, parameters = None, fetch = 'ALL') - Query and return something from the database using SQL. The following columns are apart of the "leaderboard" table: guild_id, member_id, member_name, member_level, member_xp, member_total_xp

    • Parameters
      • sql (str) SQL string used to query the database
      • parameters (Optional[Tuple[Union[str ,int]]]) The parameters used for the database query
      • fetch (Union[str, int]) The amount of rows you would like back from the query. Options: 'ALL', 'ONE', or an integer value that is greater than zero
    • Returns
      • (Union[List[tuple], tuple])
        • Using fetch='ALL' returns List[tuple]
        • Using fetch='ONE' returns tuple
        • Using fetch=4 returns List[tuple] with only four values
        • Can also return an empty list if the query was valid but got nothing from it
    • Raises
      • DatabaseFileNotFound - The database file was not found
      • LeaderboardNotFound - Table "leaderboard" in the database file is missing
      • ImproperLeaderboard - Leaderboard table was altered. Components changed or deleted
      • NotConnected - Attempted to use a method that requires a connection to a database file
      • DiscordLevelingSystemError - Argument "fetch" was the wrong type or used an invalid value
      • aiosqlite.Error - Base aiosqlite error. Multiple errors can arise from this if the SQL query was invalid
  • await switch_connection(path) - Connect to a different leveling system database file

    • Parameters
      • path (str) The location of the database file
    • Raises
      • DatabaseFileNotFound - The database file was not found
  • static method transfer(old, new, guild_id) - Transfer the database records from a database file created from v0.0.1 to a blank database file created using v0.0.2+. If you were already using a v0.0.2+ database file, there's no need to use this method

    • Parameters
      • old (str) The path of the v0.0.1 database file
      • new (str) The path of the v0.0.2+ database file
      • guild_id (int) ID of the guild that was originally used with this library
    • Raises
      • ConnectionFailure - The event loop is already running
      • DatabaseFileNotFound - "old" or "new" database file was not found
      • DiscordLevelingSystemError - One of the databases is missing the "leaderboard" table. A v0.0.2+ database file contains records, or there was an attempt to transfer records from a v0.0.2+ file to another v0.0.2+ file
  • await wipe_database(guild = None, *, intentional = False) - Delete EVERYTHING from the database. If guild is specified, only the information related to that guild will be deleted

    • Parameters
      • guild (Optional[discord.Guild]) The guild for which all information that is related to that guild will be deleted. If None, everything will be deleted
      • intentional (bool) A simple kwarg to try and ensure that this action is indeed what you want to do. Once executed, this cannot be undone
    • Raises
      • DatabaseFileNotFound - The database file was not found
      • LeaderboardNotFound - Table "leaderboard" in the database file is missing
      • ImproperLeaderboard - Leaderboard table was altered. Components changed or deleted
      • NotConnected - Attempted to use a method that requires a connection to a database file
      • FailSafe - "intentional" argument for this method was set to False in case you called this method by mistake

Migrating from v0.0.1 to v0.0.2+

Click to show details

This library was not originally designed with the use of multiple servers in mind, so all the data you might have currently (your database file was created in v0.0.1) should be from a single server. With v0.0.2, the structure of the database file was changed to accommodate this fix. That means if you are currently using a v0.0.1 database file and update to v0.0.2+, a vast majority of the library will be broken. To avoid this, you need to transfer all your v0.0.1 database file records to a v0.0.2+ database file. This can be done using the transfer method.

  • Associated static method
    • DiscordLevelingSystem.transfer(old: str, new: str, guild_id: int)

Parameters

  • old (str) The path of the v0.0.1 database file
  • new (str) The path of the v0.0.2+ database file (a brand new file from using DiscordLevelingSystem.create_database_file(path: str))
  • guild_id (int) ID of the guild that was originally used with this library
EXAMPLE
from discordLevelingSystem import DiscordLevelingSystem

old = r'C:\Users\Defxult\Documents\DiscordLevelingSystem.db'
new = r'C:\Users\Defxult\Desktop\DiscordLevelingSystem.db'

DiscordLevelingSystem.transfer(old, new, guild_id=850809412011950121)

Inserting your own leveling system information

Click to show details

Insert the records from your leveling system into this one. A lot of leveling system tutorials out there use json files to store information. Although it might work, it is insufficient because json files are not made to act as a database. Using a database file has many benefits over a json file. If you previously watched a tutorial for your leveling system and would like to import your records over to use with this library, you can do so with the below method.

  • Associated static method
    • await DiscordLevelingSystem.insert(bot: Union[Bot, AutoShardedBot], guild_id: int, users: Dict[int, int], using: str, overwrite=False, show_results=True)

Parameters

  • bot (Union[discord.ext.commands.Bot, discord.ext.commands.AutoShardedBot]) Your bot instance variable

  • guild_id (int) ID of the guild that you used your leveling system with

  • users (Dict[int, int]) This is the information that will be added to the database. The keys are user ID's, and the values are the users total XP or level. Note: This library only uses levels 0-100 and XP 0-1899250. If any number in this dict are over the levels/XP threshold, it is implicitly set back to this libraries maximum value

  • using (str) What structure your leveling system used. Options: "xp" or "levels". Some leveling systems give users only XP and they are ranked up based on that XP value. Others use a combination of levels and XP. If all the values in the users dict are based on XP, set this to "xp". If they are based on a users level, set this to "levels"

  • overwrite (bool) If a user you've specified in the users dict already has a record in the database, overwrite their current record with the one your inserting

  • show_results (bool) Print the results for how many of the users were successfully added to the database file. If any are unsuccessful, their ID along with the value you provided will also be shown

NOTE: If the users you've provided in the users dict is not currently in the guild (guild_id), their information will not be inserted. If you'd like, you can manually add those records with method DiscordLevelingSystem.add_record(), but that is discouraged because it is better to discard information that is no longer in use (the user is no longer in the guild)

EXAMPLE
# leveling_system.json
[
  {
      "5748392849348934" : {
          "xp" : 373,
          "level" : 4
      }
  },
  {
      "89283659820948923" : {
          "xp" : 23,
          "level" : 1
      }
  }
]


# bot.py
import json
from discord.ext import commands
from discordLevelingSystem import DiscordLevelingSystem

bot = commands.Bot(...)

lvl = DiscordLevelingSystem(...)
lvl.connect_to_database_file(...)

def json_to_dict() -> dict:
    with open('leveling_system.json') as fp:
        data = json.load(fp)
        formatted: Dict[int, int] = ... # format the data so all keys and values are associated with the user ID and level
        
        """
        In the end, the formatted dict should look like so:
        
        formatted = {
            5748392849348934 : 4,
            89283659820948923 : 1
        }
        """
        return formatted

@bot.command()
async def insert(ctx):
    await lvl.insert(ctx.bot, guild_id=12345678901234, users=json_to_dict(), using='levels')

bot.run(...)

discordlevelingsystem's People

Contributors

defxult avatar theonlywayup avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

discordlevelingsystem's Issues

Can't send embeds for LevelUpAnnouncement message

Hey @Defxult !
I would like to suggest a method by which we can send embeds for LevelUpAnnounecment message.

And can you please provide the link for your Discord Server if you have so that we can get help from others using this Library too :)

Thank you.

(Discord : INFINIX#7276)

Features Request

Hello @Defxult !
I would like to suggest some features which can be added to the PyPI / GitHub lib version in the next update.

1. LevelUpAnnouncement Class Attributes: (for embeds)

  • LevelUpAnnouncement.AUTHOR_AVATAR

  • LevelUpAnnouncement.AUTHOR_NAME (instead of mention)

  • LevelUpAnnouncement.GUILD_ICON (url)

2. XP / LEVEL Methods

  • DiscordLevelingSystem.add_xp(member: Member)

  • DiscordLevelingSystem.remove_xp(member: Member)

  • DiscordLevelingSystem.set_level(member: Member)

  • DiscordLevelingSystem.set_booster_role(role: Role, *, boost_amount) (eg. boost_amount : 2x, 3x, 0.5x)

3. EASY LEADERBOARD Methods

  • DiscordLevelingSystem.leaderboard(guild: Guild, message : str) (normal message)

3. ADVANCED LEADERBOARD Methods

  • DiscordLevelingSystem.leaderboard(guild: Guild, message : Embed) (embedded message)

4. NEW CLASS For Leaderboard

  • Class Attributes for Leaderboard : (discord.Embed)

  • Leaderboard.MEMBER_NAME

  • Leaderboard.MEMBER_MENTION

  • Leaderboard.GUILD_ICON (url)

Thanks.

(Discord : INFINIX#7276)

How can I fix this event loop?

Describe the bug
I'm trying to create a rank command in commands.Cog but when I try to start the discord bot it says me about "the event loop is already running" how can I fix it

To Reproduce
Minimal code to reproduce the bug/error
import discord, random
from discord.ext import commands
from discord import app_commands, File
from easy_pil import Editor, load_image_async, Font
from discordLevelingSystem import DiscordLevelingSystem, LevelUpAnnouncement

announcement = LevelUpAnnouncement(f"ยกGG {LevelUpAnnouncement.Member.mention}, you just advanced to level {LevelUpAnnouncement.LEVEL} <a:gigachad:1043899515754782740>!")
lvl = DiscordLevelingSystem(rate=6,per=20.0,level_up_announcement=announcement)
lvl.connect_to_database_file(r'C:\Users\Personal\Desktop\Mimi-Bot\DiscordLevelingSystem.db')

DiscordLevelingSystem.create_database_file(r'C:\Users\Personal\Desktop\Mimi-Bot')

class ranks(commands.Cog):
def init(self, client):
self.client = client

@commands.Cog.listener()
async def on_ready(self):
    print("Loaded ranks")

@commands.Cog.listener()
async def on_message(self, message):
    await lvl.award_xp(amount=25, message=message)

@app_commands.command(name="rank", description="ยกGet your actual rank!")
@app_commands.checks.cooldown(1, 5.0, key=lambda i: (i.guild_id, i.user.id))
async def rank(self, interaction: discord.Interaction, member: discord.Member=None):
    if member == None:
        member = interaction.user
        data = await lvl.get_data_for(member)
        xp_total_lvl_up = DiscordLevelingSystem.get_xp_for_level(data.level+1)
        xp_actual = await lvl.get_xp_for(member)
    data = await lvl.get_data_for(member)
    xp_total_lvl_up = DiscordLevelingSystem.get_xp_for_level(data.level+1)
    xp_actual = await lvl.get_xp_for(member)
    percentage = int(((xp_actual * 100)/ xp_total_lvl_up))
    if percentage < 1:
        percentage = 0
    ## Rank card
    background = Editor(f"{0}.png")
    profile = await load_image_async(str(member.avatar.url))
    profile = Editor(profile).resize((150, 150)).circle_image()
    poppins = Font.poppins(size=40)
    poppins_small = Font.poppins(size=30)
    background.paste(profile.image, (30, 30))
    background.rectangle((30, 220), width=650, height=40, fill="#fff", radius=20)
    background.bar(
        (30, 220),
        max_width=650,
        height=40,
        percentage=percentage,
        fill="#ff9933",
        radius=20,
    )
    background.text((200, 40), str(member.name), font=poppins, color="#ff9933")
    background.rectangle((200, 100), width=350, height=2, fill="#ff9933")
    background.text(
        (200, 130),
        f"Level : {data.level}   "
        + f" XP : {xp_actual} / {xp_total_lvl_up}   " + f"Rank: {data.rank}",
        font=poppins_small,
        color="#ff9933",
    )
    card = File(fp=background.image_bytes, filename="zCARD.png")
    await interaction.response.send_message(file=card)

@rank.error
async def on_rank_error(self, interaction: discord.Interaction, error: app_commands.AppCommandError):
    messages = ["ยกBro, calm down!", "ยกBro, slow!", "ยกJust be patient!", "ยกYou have to wait!"]
    if isinstance(error, app_commands.CommandOnCooldown):
        await interaction.response.send_message(content=random.choice(messages), ephemeral=True)
    elif isinstance(error, app_commands.AppCommandError):
        await interaction.response.send_message(f"Error: {error}", ephemeral=True)

async def setup(client):
await client.add_cog(ranks(client))

Traceback if any
2022-11-27 19:07:55 ERROR discord.client Ignoring exception in on_ready
Traceback (most recent call last):
File "C:\Users\Personal\AppData\Local\Programs\Python\Python310\lib\site-packages\discordLevelingSystem\leveling_system.py", line 359, in connect_to_database_file
self._connection = self._loop.run_until_complete(aiosqlite.connect(path))
File "C:\Users\Personal\AppData\Local\Programs\Python\Python310\lib\asyncio\base_events.py", line 622, in run_until_complete
self._check_running()
File "C:\Users\Personal\AppData\Local\Programs\Python\Python310\lib\asyncio\base_events.py", line 582, in _check_running
raise RuntimeError('This event loop is already running')
RuntimeError: This event loop is already running

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "C:\Users\Personal\AppData\Local\Programs\Python\Python310\lib\site-packages\discord\ext\commands\bot.py", line 934, in _load_from_module_spec
spec.loader.exec_module(lib) # type: ignore
File "", line 883, in exec_module
File "", line 241, in _call_with_frames_removed
File "c:\Users\Personal\Desktop\Mimi-Bot\cogs\rank.py", line 9, in
lvl.connect_to_database_file(r'C:\Users\Personal\Desktop\Mimi-Bot\DiscordLevelingSystem.db')
File "C:\Users\Personal\AppData\Local\Programs\Python\Python310\lib\site-packages\discordLevelingSystem\leveling_system.py", line 363, in connect_to_database_file
raise ConnectionFailure
discordLevelingSystem.errors.ConnectionFailure: Cannot connect to database file because the event loop is already running

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
File "C:\Users\Personal\AppData\Local\Programs\Python\Python310\lib\site-packages\discord\client.py", line 409, in _run_event
await coro(*args, **kwargs)
File "c:\Users\Personal\Desktop\Mimi-Bot\main.py", line 15, in on_ready
await client.load_extension(f"cogs.{file[:-3]}")
File "C:\Users\Personal\AppData\Local\Programs\Python\Python310\lib\site-packages\discord\ext\commands\bot.py", line 1012, in load_extension
await self._load_from_module_spec(spec, name)
File "C:\Users\Personal\AppData\Local\Programs\Python\Python310\lib\site-packages\discord\ext\commands\bot.py", line 937, in _load_from_module_spec
raise errors.ExtensionFailed(key, e) from e
discord.ext.commands.errors.ExtensionFailed: Extension 'cogs.rank' raised an error: ConnectionFailure: Cannot connect to database file because the event loop is already running

Required Checklist

  • I am using discord.py 2.0 or higher
  • I am using discordLevelingSystem 1.2.0 or higher
  • I have the required intents enabled

Version Info
What's the exact version of discordLevelingSystem/discord.py are you using?

  • discord.py: [2.1.0]
  • discordLevelingSystem: [1.2.0]

ABCMeta' object is not subscriptable

python version 3.8.10
pycord 2.0

Traceback (most recent call last):
File "/mnt/sharessd/code/python/utachi-refractor/ablelevel.py", line 3, in
from discordLevelingSystem import DiscordLevelingSystem
File "/home/pooh/.local/share/virtualenvs/utachi-refractor-hCTN_qea/lib/python3.8/site-packages/discordLevelingSystem/init.py", line 12, in
from .announcement import LevelUpAnnouncement
File "/home/pooh/.local/share/virtualenvs/utachi-refractor-hCTN_qea/lib/python3.8/site-packages/discordLevelingSystem/announcement.py", line 70, in
class LevelUpAnnouncement:
File "/home/pooh/.local/share/virtualenvs/utachi-refractor-hCTN_qea/lib/python3.8/site-packages/discordLevelingSystem/announcement.py", line 141, in LevelUpAnnouncement
def init(self, message: Union[str, Embed]=default_message, level_up_channel_ids: Optional[Sequence[int]]=None, allowed_mentions: AllowedMentions=default_mentions, tts: bool=False, delete_after: Optional[float]=None):
TypeError: 'ABCMeta' object is not subscriptable

[Feature Request] Add DiscordLevelingSystem.get_xp_for_level()

I don't know if this is possible or not but this method would return an int based on the amount of xp needed for a level

It's similar to next_level_up() but instead of returning the remaining amount it returns the total amount

Example:

xp_needed_for_level_2 = await DiscordLevelingSystem.get_xp_for_level(2)
print(xp_needed_for_level_2)
# >>> 200 (Example int)

lvl has not attribute inter.guild or ctx.guild.

Hello,
con you fix that as fast as possible please.

This is my top command:

    @commands.command(name="top", description="Guarda la classifica del tuo server.")
    async def top(inter: commands.Context, self):
        data = await self.lvl.each_member_data(inter.guild, sort_by='rank')
        top_10 = data[:10]
        leaderboard_message = "Top 10 Leaderboard:\n"
        for member_data in top_10:
            member = inter.guild.get_member(member_data['user_id'])
            leaderboard_message += f"{member.display_name}: Rank {member_data['rank']}\n"
        await inter.send(leaderboard_message)

This is the traceback

Traceback (most recent call last):
  File "/home/container/.local/lib/python3.11/site-packages/discord/ext/commands/core.py", line 235, in wrapped
    ret = await coro(*args, **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/container/cogs/leveling.py", line 47, in top
    data = await self.lvl.each_member_data(inter.guild, sort_by='rank')
                 ^^^^^^^^
AttributeError: 'Context' object has no attribute 'lvl'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/container/.local/lib/python3.11/site-packages/discord/ext/commands/bot.py", line 1350, in invoke
    await ctx.command.invoke(ctx)
  File "/home/container/.local/lib/python3.11/site-packages/discord/ext/commands/core.py", line 1029, in invoke
    await injected(*ctx.args, **ctx.kwargs)  # type: ignore
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/container/.local/lib/python3.11/site-packages/discord/ext/commands/core.py", line 244, in wrapped
    raise CommandInvokeError(exc) from exc
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: AttributeError: 'Context' object has no attribute 'lvl'

Required Checklist

  • I am using discord.py 2.0 or higher
  • I am using discordLevelingSystem 1.2.0 or higher
  • I have the required intents enabled

Add support for MongoDB

Hi, i would like to request mongodb integration. as i want to host my py-cord bot on heroku and since heroku has ephemeral storage system, the .db files would be cleared out periodically.
Hence i request support for mongodb

open for contribution?

Is the repository open for contribution?

If it is open I would love to work on some leveling cards for more user customization...

using PIL i made this and would live to implement here, if possible

image

also i have some ideas for leaderboard
I would love to work on a fork if i am allowed to make something like this!

add_xp() doesn't add to member_xp but only to member_total_xp

Describe the bug
When doing add_xp() the xp is added only to the user's total xp and the current xp is not updated. When I consult the database I can see that lxp is added but only to the total xp.

To Reproduce
Minimal code to reproduce the bug/error

import discord
from discordLevelingSystem import DiscordLevelingSystem, RoleAward, LevelUpAnnouncement
lvl = DiscordLevelingSystem(level_up_announcement=announcement, no_xp_channels=[1022441601420759100])
announcement = LevelUpAnnouncement(f'{LevelUpAnnouncement.Member.mention} just leveled up to level {LevelUpAnnouncement.LEVEL} ๐Ÿ˜Ž')
lvl.connect_to_database_file(r'database path')
bot = discord.Bot(intents=discord.Intents(messages=True, guilds=True, members=True))
@bot.event
async def on_voice_state_update(member, before, after):
  print("Voice state updated")
  if before.channel is None and after.channel is not None:
    print("User joined a voice channel")
    await lvl.add_xp(member, 20)
    print("20 xp added to user")
    print(member)

Traceback if any

N/A

Required Checklist

  • I am using discord.py 2.0 or higher
  • I am using discordLevelingSystem 1.2.0 or higher
  • I have the required intents enabled

Version Info
What's the exact version of discordLevelingSystem/discord.py are you using?

  • discord.py:2.1.3
  • discordLevelingSystem:1.2.0

sorting users by rank is incorrect

Describe the bug
leaderboard doesn't sort users correctly

You're on the level 3 (#2) and you have 608 XP!

Top 1: someone โ„–1
Top 2: someone โ„–2
Top 3: someone else

while should be:

Top 1: someone โ„–1
Top 2: TheRjavisDev
Top 3: someone else

To Reproduce
Minimal code to reproduce the bug/error

    data = await lvl.each_member_data(ctx.guild,sort_by='rank')
    if data[1].name and data[2].name and data[3].name:
        await ctx.respond(f"Top 1: {data[1].name} ({data[1].level})\nTop 2: {data[2].name} ({data[2].level})\nTop 3: {data[3].name} ({data[3].level})")
    else:
        await ctx.respond("Not enough members to display leaderboard!")

Traceback if any

N/A

Required Checklist

  • I am using discord.py 2.0 or higher (actually running pycord)
  • I am using discordLevelingSystem 1.2.0 or higher
  • I have the required intents enabled

Version Info
What's the exact version of discordLevelingSystem/pycord are you using?

  • pycord: 2.3
  • discordLevelingSystem: 1.20

I'm not sure is it a pycord or discordlevelingsystem issue, so you can close it if it's invalid

one more thing: total_xp is not being accepted by sort_by

Found a bug. Needs to be fixed urgently.

Hi @Defxult !
I just found a new bug while I was testing my code.


ISSUE:

  • The issue I am currently facing is, the database file (.db) created using the DiscordLevelingSystem.create_database_file(file_path: str) method only registers the member id as the primary key in leaderboard table, while the guild id doesn't get registered.
  • I checked out the code of class DiscordLevelingSystem (leveling_system.py), I couldn't find any code which registers the guild in the leaderboard table. This means the bot would only update the leaderboard table when the member/author sends a message, regardless of being in the same guild or a different one.
  • I levelled up to 1 in a server and my next level in another server was level 2 (while testing the code).
  • The member's level and XP shouldn't remain the same in all the servers.

Probably not a bug:

  • The member data cannot be accessed from await DiscordLevelingSystem.get_data_for(member: Member) method.
  • Please provide an example using the class MemberData in the readme.md file.

Requesting you to please resolve this issue as soon as possible.
Thank you

Installation problem

Describe the bug
When I try to install discordLevelingSystem, a error message appear, and I don't know what cause this, I laready tried to change python version, and nothing changed, tried --upgrade (still nothing), so I don't know what to do. (yes discord is installed on my computer ( I used pip install discord --upgrade), but it still say ModuleNotFoundError: No module named 'discord' ).

To Reproduce
pip install discordLevelingSystem

Traceback if any

Collecting discordLevelingSystem (from -r .\requirements.txt (line 7))
  Using cached discordLevelingSystem-1.2.0.tar.gz (58 kB)
  Installing build dependencies ... done
  Getting requirements to build wheel ... error
  error: subprocess-exited-with-error

  ร— Getting requirements to build wheel did not run successfully.
  โ”‚ exit code: 1
  โ•ฐโ”€> [21 lines of output]
      Traceback (most recent call last):
        File "C:\Users\ender\AppData\Local\Programs\Python\Python310\lib\site-packages\pip\_vendor\pyproject_hooks\_in_process\_in_process.py", line 353, in <module>
          main()
        File "C:\Users\ender\AppData\Local\Programs\Python\Python310\lib\site-packages\pip\_vendor\pyproject_hooks\_in_process\_in_process.py", line 335, in main
          json_out['return_val'] = hook(**hook_input['kwargs'])
        File "C:\Users\ender\AppData\Local\Programs\Python\Python310\lib\site-packages\pip\_vendor\pyproject_hooks\_in_process\_in_process.py", line 118, in get_requires_for_build_wheel
          return hook(config_settings)
        File "C:\Users\ender\AppData\Local\Temp\pip-build-env-_ab12cde\overlay\Lib\site-packages\setuptools\build_meta.py", line 341, in get_requires_for_build_wheel
          return self._get_build_requires(config_settings, requirements=['wheel'])
        File "C:\Users\ender\AppData\Local\Temp\pip-build-env-_ab12cde\overlay\Lib\site-packages\setuptools\build_meta.py", line 323, in _get_build_requires
          self.run_setup()
        File "C:\Users\ender\AppData\Local\Temp\pip-build-env-_ab12cde\overlay\Lib\site-packages\setuptools\build_meta.py", line 487, in run_setup
          super(_BuildMetaLegacyBackend,
        File "C:\Users\ender\AppData\Local\Temp\pip-build-env-_ab12cde\overlay\Lib\site-packages\setuptools\build_meta.py", line 338, in run_setup
          exec(code, locals())
        File "<string>", line 2, in <module>
        File "C:\Users\ender\AppData\Local\Temp\pip-install-fghi23jk\discordlevelingsystem_8e83c0b019e441568f860ceb21d4a8fd\discordLevelingSystem\__init__.py", line 12, in <module>
          from .announcement import LevelUpAnnouncement
        File "C:\Users\ender\AppData\Local\Temp\pip-install-fghi23jk\discordlevelingsystem_8e83c0b019e441568f860ceb21d4a8fd\discordLevelingSystem\announcement.py", line 28, in <module>
          from discord import AllowedMentions, Embed, Member as DMember
      ModuleNotFoundError: No module named 'discord'
      [end of output]

  note: This error originates from a subprocess, and is likely not a problem with pip.
error: subprocess-exited-with-error

ร— Getting requirements to build wheel did not run successfully.
โ”‚ exit code: 1
โ•ฐโ”€> See above for output.

note: This error originates from a subprocess, and is likely not a problem with pip.

Required Checklist

  • I am using discord.py 2.0 or higher
  • (N/A) I am using discordLevelingSystem 1.2.0 or higher
  • (N/A) I have the required intents enabled

Version Info
What's the exact version of discordLevelingSystem/discord.py are you using?

  • discord.py: pip show discord.py Name: discord.py Version: 2.2.3 Summary: A Python wrapper for the Discord API Home-page: https://github.com/Rapptz/discord.py
  • discordLevelingSystem: N/A

Users or Guilds without icons/avatars break the bot

Users or Guilds without icons/avatars break the bot
If something doesn't have an avatar, it'll give an error

To Reproduce
Use the bare minimum code in the README

Traceback if any

Traceback (most recent call last):
  File "C:\Users\censor\AppData\Local\Programs\Python\Python310\lib\site-packages\discord\client.py", line 382, in _run_event
    await coro(*args, **kwargs)
  File "c:\Users\censor\Documents\Github\file-storage\Robot Maxx\cogs\levels.py", line 41, in on_message
    await self.lvl.award_xp(amount=25, message=message)
  File "C:\Users\censor\AppData\Local\Programs\Python\Python310\lib\site-packages\discordLevelingSystem\decorators.py", line 62, in wrapper
    return await func(*args, **kwargs)
  File "C:\Users\censor\AppData\Local\Programs\Python\Python310\lib\site-packages\discordLevelingSystem\decorators.py", line 80, in wrapper
    return await func(*args, **kwargs)
  File "C:\Users\censor\AppData\Local\Programs\Python\Python310\lib\site-packages\discordLevelingSystem\decorators.py", line 104, in wrapper
    return await func(*args, **kwargs)
  File "C:\Users\censor\AppData\Local\Programs\Python\Python310\lib\site-packages\discordLevelingSystem\leveling_system.py", line 1899, in award_xp
    await self._handle_level_up(message, md, leveled_up=member_level_up)
  File "C:\Users\censor\AppData\Local\Programs\Python\Python310\lib\site-packages\discordLevelingSystem\leveling_system.py", line 1713, in _handle_level_up
    announcement_message = lua._parse_message(lua.message, self._message_author)
  File "C:\Users\censor\AppData\Local\Programs\Python\Python310\lib\site-packages\discordLevelingSystem\announcement.py", line 212, in _parse_message
    full = self._convert_member_markdown(partial, message_author)
  File "C:\Users\censor\AppData\Local\Programs\Python\Python310\lib\site-packages\discordLevelingSystem\announcement.py", line 180, in _convert_member_markdown
    AnnouncementMember.avatar_url : message_author.avatar.url,
AttributeError: 'NoneType' object has no attribute 'url'

Required Checklist

  • My Python version is 3.8 or higher
  • I am using discord.py 2.0 or higher
  • I have the required intents enabled

py_cord-2.0.0.dist-info
discordLevelingSystem-1.1.0-py3.10.egg-info

Feature request of getting access to use database connection in an async loop

Hi @Defxult!
First of all, I would appreciate your contribution to this library.
I did check out version 0.0.2 of the library. It is very nice. Thanks a lot for adding those features.
But, I am facing some issues with it.


ISSUE

  • As I am using an SQLite connection for my database, I am not able to fetch the guild id for per-server configuration, i.e., level_up_channel_id (an optional parameter for the LevelUpAnnouncement class), if not in an async loop.
  • I need my code to work like this:
cur = conn.cursor()
predicate = cur.execute('SELECT * FROM config WHERE guild_id=?', (object.guild.id,))
predicate = predicate.fetchone()
if predicate[1]:
  level_up_channel = predicate[7] #predicate[7] here, is the level_up_channel_id stored in the db
  announcement = LevelUpAnnouncement(message = discord.Embed(title=f"<a:pepeclap:850247977943040001> CONGRATULATIONS!", description=f"{LevelUpAnnouncement.Member.mention} JUST LEVELED UP TO **LEVEL {LevelUpAnnouncement.LEVEL}**!", color = discord.Color.gold()) .set_thumbnail(url = LevelUpAnnouncement.Member.avatar_url) .set_footer(icon_url = LevelUpAnnouncement.Member.Guild.icon_url, text="MADE BY INFINIX#7276"), level_up_channel_id = level_up_channel)
  • If we get access to use the DiscordLevelingSystem.connect_to_database_file in an async func like on_message_event, we can easily fetch the guild's id by message.guild.id.
  • I am sorry if I am wrong because I am new to SQLite database.

Thanks.

User isn't gaining experience or going into the database

Describe the bug
For some reason, award_xp isn't putting the user into the database or giving experience even if I use add_record to put them in.

To Reproduce
Minimal code to reproduce the bug/error
I have written the bare minimum to get a bot running with this code, as this is how I have my bot written.

import discord
import asyncio
from discordLevelingSystem import DiscordLevelingSystem
from discordLevelingSystem.errors import DatabaseFileNotFound, LeaderboardNotFound, ImproperLeaderboard, NotConnected,\
    ConnectionFailure, DiscordLevelingSystemError
from discord.ext import commands
from discord.ext.commands import AutoShardedBot
from pathlib import Path
from config import donator_role, prefixes, token

intents = discord.Intents.default()
intents.message_content = True
intents.members = True
intents.presences = True

if not Path(r'database/DiscordLevelingSystem.db').is_file():
    try:
        DiscordLevelingSystem.create_database_file(r'./database/')
    except (ConnectionFailure, DatabaseFileNotFound) as error:
        raise error


class DiscordBot(AutoShardedBot):

    def __init__(self):
        super().__init__(
            command_prefix=commands.when_mentioned_or(*prefixes),
            intents=intents
        )

    lvl = DiscordLevelingSystem(rate=1, per=60.0, awards=None, announce_level_up=True, stack_awards=True)

    async def on_message(self, message: discord.Message) -> None:
        if message.author == self.user or message.author.bot:
            return
        await self.process_commands(message)
        try:
            await self.lvl.add_record(
                guild_id=message.guild.id,
                member_id=message.author.id,
                member_name=message.author.name,
                level=0
            )
        except DiscordLevelingSystemError:
            raise DiscordLevelingSystemError
        try:
            await self.lvl.award_xp(
                amount=[15, 25],
                message=message,
                refresh_name=True,
                bonus=DiscordLevelingSystem(
                    donator_role,
                    20,
                    multiply=False
                )
            )
        except (DatabaseFileNotFound, LeaderboardNotFound, ImproperLeaderboard, NotConnected) as e:
            raise e


if __name__ == "__main__":
    try:
        DiscordBot.lvl.connect_to_database_file(r'database/DiscordLevelingSystem.db')
    except (ConnectionFailure, DatabaseFileNotFound) as error:
        raise error
    asyncio.run(DiscordBot().start(token=token))

Traceback if any

N/A

Required Checklist

  • I am using discord.py 2.0 or higher
  • I am using discordLevelingSystem 1.2.0 or higher
  • I have the required intents enabled

Version Info
What's the exact version of discordLevelingSystem/discord.py are you using?

  • discord.py: [2.1.0]
  • discordLevelingSystem: [1.2.0]

Feature request: Level promotion (json to database)

Hi @Defxult,

First of thank you for sharing this awesome set of levelling tools.
I would like to ask if it would it be possible to add level promotion, what I mean by this at each level cap the experience accumulations stops until a faction to 'promote' a user is called.

Example:

  • Level 0 to 1 is 100 xp
  • When user reaches 100 xp, xp accumulation stops for that user and 'locks' them to level 0 with 100xp
  • Then when an 'unlock'/promote function is called the level up from 0 to 1 is granted and the level up message is send.

In my mind it would be an setting that skips automated advancement of the user and lets that behaviour be decided outside the library.

I hope it is something that can be added as it would save me allot of time having to start a level system from scratch.

Level per XP

Hello !
I just wanted to know how do I set a maximum xp for each level?
for example 100 XP for 1 Level

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.