├── .env.example ├── .gitignore ├── LICENSE ├── README.md ├── bot.py ├── card.png ├── leveling ├── __init__.py ├── assets │ └── bg.png ├── cogs │ └── leveling.py ├── config.py └── utils │ ├── __init__.py │ └── sql.py └── requirements.txt /.env.example: -------------------------------------------------------------------------------- 1 | # Rename this file to `.env` 2 | TOKEN=ODU3OTAzMDg1NjY1NTE3NTg4.YNWWgg.JgpdLoJKu5dh1ov_mjOoSbAnj24 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | venv/ 3 | __pycache__/ 4 | test.py -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Shahriyar#9770 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Discord leveling 2 | A simple leveling bot for discord, made using python 3 | 4 | ### Setup 5 | - Install postgresql 6 | - Create a new user and give it a password and login permission. Optionally Superuser permission 7 | - Create a database and select the newly created user as its owner 8 | - Edit leveling/config.py and fill the Database info 9 | - Install packages from requirements.txt `pip install -r requirements.txt` 10 | - Run bot.py -------------------------------------------------------------------------------- /bot.py: -------------------------------------------------------------------------------- 1 | import os 2 | from leveling import bot 3 | from dotenv import load_dotenv 4 | 5 | load_dotenv(".env") 6 | TOKEN = os.getenv("TOKEN") 7 | 8 | bot.run(TOKEN) 9 | -------------------------------------------------------------------------------- /card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahriyardx/discordpy-leveling-bot/04336fa582484e0b18a3d82ae9e33a560e2173dd/card.png -------------------------------------------------------------------------------- /leveling/__init__.py: -------------------------------------------------------------------------------- 1 | import asyncpg 2 | from discord.ext import commands 3 | from leveling.config import db_config 4 | from leveling.utils import create_tables, increase_xp 5 | 6 | 7 | class Leveling(commands.Bot): 8 | def __init__(self, **options): 9 | super().__init__(**options) 10 | 11 | async def on_ready(self): 12 | await self.connect_db() 13 | await self.load_cogs() 14 | 15 | print("Bot is ready") 16 | 17 | async def load_cogs(self): 18 | self.load_extension("leveling.cogs.leveling") 19 | 20 | async def connect_db(self): 21 | self.db = await asyncpg.create_pool(**db_config) 22 | await create_tables(self.db) 23 | 24 | async def on_message(self, message): 25 | await self.process_commands(message) 26 | 27 | if message.author.bot: 28 | return 29 | 30 | await increase_xp(self.db, message) 31 | 32 | 33 | bot = Leveling(command_prefix="?") 34 | -------------------------------------------------------------------------------- /leveling/assets/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shahriyardx/discordpy-leveling-bot/04336fa582484e0b18a3d82ae9e33a560e2173dd/leveling/assets/bg.png -------------------------------------------------------------------------------- /leveling/cogs/leveling.py: -------------------------------------------------------------------------------- 1 | from discord import File, Member 2 | from discord.ext import commands 3 | from leveling.utils import get_user_data, get_rank 4 | from easy_pil import Editor, Canvas, load_image_async, Font 5 | 6 | 7 | class Level(commands.Cog): 8 | def __init__(self, bot) -> None: 9 | self.bot = bot 10 | 11 | @commands.command() 12 | async def rank(self, ctx, member: Member = None): 13 | if not member: 14 | member = ctx.author 15 | 16 | user_data = await get_user_data(self.bot.db, member.id, ctx.guild.id) 17 | # rank = await get_rank(self.bot.db, member.id, ctx.guild.id) # in case of using rank 18 | 19 | next_level_xp = (user_data["level"] + 1) * 100 20 | current_level_xp = user_data["level"] * 100 21 | xp_need = next_level_xp - current_level_xp 22 | xp_have = user_data["xp"] - current_level_xp 23 | 24 | percentage = (xp_need / 100) * xp_have 25 | 26 | ## Rank card 27 | background = Editor("leveling/assets/bg.png") 28 | profile = await load_image_async(str(member.avatar_url)) 29 | 30 | profile = Editor(profile).resize((150, 150)).circle_image() 31 | 32 | poppins = Font().poppins(size=40) 33 | poppins_small = Font().poppins(size=30) 34 | 35 | square = Canvas((500, 500), "#06FFBF") 36 | square = Editor(square) 37 | square.rotate(30, expand=True) 38 | 39 | background.paste(square.image, (600, -250)) 40 | background.paste(profile.image, (30, 30)) 41 | 42 | background.rectangle((30, 220), width=650, height=40, fill="white", radius=20) 43 | background.bar( 44 | (30, 220), 45 | max_width=650, 46 | height=40, 47 | percentage=percentage, 48 | fill="#FF56B2", 49 | radius=20, 50 | ) 51 | background.text((200, 40), str(member), font=poppins, color="white") 52 | 53 | background.rectangle((200, 100), width=350, height=2, fill="#17F3F6") 54 | background.text( 55 | (200, 130), 56 | f"Level : {user_data['level']}" 57 | + f" XP : {user_data['xp']} / {(user_data['level'] + 1) * 100}", 58 | font=poppins_small, 59 | color="white", 60 | ) 61 | 62 | file = File(fp=background.image_bytes, filename="card.png") 63 | await ctx.send(file=file) 64 | 65 | 66 | def setup(bot): 67 | bot.add_cog(Level(bot)) 68 | -------------------------------------------------------------------------------- /leveling/config.py: -------------------------------------------------------------------------------- 1 | db_config = { 2 | "host": "localhost", 3 | "port": 5432, 4 | "user": "shah", 5 | "password": "shah", 6 | "database": "leveling", 7 | } 8 | -------------------------------------------------------------------------------- /leveling/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .sql import create_tables, increase_xp, get_user_data, get_rank 2 | -------------------------------------------------------------------------------- /leveling/utils/sql.py: -------------------------------------------------------------------------------- 1 | from asyncpg import Pool 2 | 3 | 4 | async def create_tables(pool: Pool): 5 | async with pool.acquire() as connection: 6 | # await connection.execute("DROP TABLE IF EXISTS users") # Uncomment this line if you have the database already created and have old data 7 | 8 | await connection.execute( 9 | """ 10 | CREATE TABLE IF NOT EXISTS users( 11 | id BIGSERIAL PRIMARY KEY NOT NULL, 12 | user_id BIGINT NOT NULL, 13 | guild_id BIGINT NOT NULL, 14 | xp BIGINT NOT NULL DEFAULT 5, 15 | level BIGINT NOT NULL DEFAULT 0 16 | ) 17 | """ 18 | ) 19 | 20 | 21 | async def create_user(pool: Pool, user_id, guild_id): 22 | async with pool.acquire() as connection: 23 | record = await connection.fetchrow( 24 | "SELECT * FROM users WHERE user_id=$1 AND guild_id=$2", user_id, guild_id 25 | ) 26 | if record: 27 | return 28 | 29 | await connection.execute( 30 | "INSERT INTO users(user_id, guild_id) VALUES($1, $2)", user_id, guild_id 31 | ) 32 | 33 | 34 | async def increase_xp(pool: Pool, message, rate=5): 35 | await create_user(pool, message.author.id, message.guild.id) 36 | 37 | async with pool.acquire() as connection: 38 | record = await connection.fetchrow( 39 | "SELECT * FROM users WHERE user_id=$1 AND guild_id=$2", message.author.id, message.guild.id 40 | ) 41 | xp = record["xp"] 42 | level = record["level"] 43 | new_level = int((xp + rate) / 100) 44 | 45 | if new_level > level: 46 | new_level = new_level 47 | else: 48 | new_level = level 49 | 50 | await connection.execute( 51 | "UPDATE users SET xp = $1, level = $2 WHERE user_id = $3 AND guild_id=$4", 52 | xp + rate, 53 | new_level, 54 | message.author.id, 55 | message.guild.id, 56 | ) 57 | 58 | 59 | async def get_user_data(pool: Pool, user_id, guild_id): 60 | await create_user(pool, user_id, guild_id) 61 | 62 | async with pool.acquire() as connection: 63 | record = await connection.fetchrow( 64 | "SELECT * FROM users WHERE user_id=$1 AND guild_id=$2", user_id, guild_id 65 | ) 66 | return dict(record) 67 | 68 | 69 | async def get_rank(pool: Pool, user_id, guild_id): 70 | await create_user(pool, user_id, guild_id) 71 | 72 | async with pool.acquire() as connection: 73 | records = await connection.fetch( 74 | "SELECT * FROM users WHERE guild_id=$1 ORDER BY xp DESC", guild_id 75 | ) 76 | rank = 0 77 | for record in records: 78 | rank += 1 79 | if record["user_id"] == user_id: 80 | break 81 | 82 | return rank 83 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asyncpg==0.23.0 2 | discord==1.7.3 3 | discord.py==1.7.3 4 | disrank==0.0.2 5 | python-dotenv==0.18.0 6 | easy-pil==0.0.2 7 | 8 | --------------------------------------------------------------------------------