├── requirements.txt ├── README.md ├── config.json ├── base ├── struct.py └── utilities.py ├── bot.py └── cogs └── leveling.py /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp>=3.5.4 2 | discord.py>=1.1.1 3 | asyncpg>=0.18.3 4 | Pillow>=6.0.0 5 | discord>=1.0.1 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # discord-bot-leveling-system 2 | Discord bot example for global leveling system with rank cards. 3 | 4 | ## Screenshot 5 | ![alt text](https://i.imgur.com/5s3AXKJ.png) 6 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "bot_token": "token", 3 | "postgresql_user": "postgres", 4 | "postgresql_password": "password", 5 | "min_message_xp": 10, 6 | "max_message_xp": 30 7 | } -------------------------------------------------------------------------------- /base/struct.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | 4 | class Config: 5 | def __init__(self, cfg: dict) -> None: 6 | self.bot_token = cfg['bot_token'] 7 | self.postgresql_user = cfg['postgresql_user'] 8 | self.postgresql_password = cfg['postgresql_password'] 9 | self.min_message_xp = cfg['min_message_xp'] 10 | self.max_message_xp = cfg['max_message_xp'] 11 | -------------------------------------------------------------------------------- /bot.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from discord.ext import commands 3 | from base.struct import Config 4 | import json 5 | 6 | 7 | class Bot(commands.Bot): 8 | def __init__(self): 9 | super().__init__( 10 | command_prefix='.', 11 | description='Discord bot leveling system example.' 12 | ) 13 | 14 | with open('config.json', 'r') as f: 15 | self.cfg = Config(json.loads(f.read())) 16 | 17 | self.cog_list = ['cogs.leveling'] 18 | for cog in self.cog_list: 19 | try: 20 | self.load_extension(cog) 21 | except Exception as e: 22 | print(f'Error occured while cog "{cog}" was loaded.\n{e}') 23 | 24 | def startup(self): 25 | self.run(self.cfg.bot_token) 26 | 27 | 28 | if __name__ == '__main__': 29 | Bot().startup() 30 | -------------------------------------------------------------------------------- /base/utilities.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from io import BytesIO 3 | from PIL import Image, ImageDraw, ImageFont 4 | import asyncpg 5 | 6 | 7 | class Database: 8 | def __init__(self, loop, user: str, password: str) -> None: 9 | self.user = user 10 | self.password = password 11 | loop.create_task(self.connect()) 12 | 13 | async def connect(self) -> None: 14 | self.conn = await asyncpg.connect(user=self.user, password=self.password, host='127.0.0.1') 15 | exists = await self.conn.fetch('SELECT datname FROM pg_catalog.pg_database WHERE datname=\'discordleveling\'') 16 | if not exists: 17 | await self.conn.fetch('CREATE DATABASE discordleveling') 18 | await self.conn.close() 19 | self.conn = await asyncpg.connect(user=self.user, password=self.password, database='discordleveling', host='127.0.0.1') 20 | await self.conn.fetch('CREATE TABLE IF NOT EXISTS users (id TEXT NOT NULL, rank INT NOT NULL, xp INT NOT NULL)') 21 | 22 | async def fetch(self, sql: str) -> list: 23 | return await self.conn.fetch(sql) 24 | 25 | 26 | class Rank: 27 | def __init__(self) -> None: 28 | self.font = ImageFont.truetype('arialbd.ttf', 28) 29 | self.medium_font = ImageFont.truetype('arialbd.ttf', 22) 30 | self.small_font = ImageFont.truetype('arialbd.ttf', 16) 31 | 32 | def draw(self, user: str, rank: str, xp: str, profile_bytes: BytesIO) -> BytesIO: 33 | profile_bytes = Image.open(profile_bytes) 34 | im = Image.new('RGBA', (400, 148), (44, 44, 44, 255)) 35 | 36 | im_draw = ImageDraw.Draw(im) 37 | im_draw.text((154, 5), user, font=self.font, fill=(255, 255, 255, 255)) 38 | 39 | rank_text = f'RANK {rank}' 40 | im_draw.text((154, 37), rank_text, font=self.medium_font, fill=(255, 255, 255, 255)) 41 | 42 | needed_xp = self.neededxp(rank) 43 | xp_text = f'{xp}/{needed_xp}' 44 | im_draw.text((154, 62), xp_text, font=self.small_font, fill=(255, 255, 255, 255)) 45 | 46 | im_draw.rectangle((174, 95, 374, 125), fill=(64, 64, 64, 255)) 47 | im_draw.rectangle((174, 95, 174+(int(xp/needed_xp*100))*2, 125), fill=(221, 221, 221, 255)) 48 | 49 | im_draw.rectangle((0, 0, 148, 148), fill=(255, 255, 255, 255)) 50 | im.paste(profile_bytes, (10, 10)) 51 | 52 | buffer = BytesIO() 53 | im.save(buffer, 'png') 54 | buffer.seek(0) 55 | 56 | return buffer 57 | 58 | @staticmethod 59 | def neededxp(level: str) -> int: 60 | return 100+level*80 61 | 62 | 63 | class Utilities: 64 | def __init__(self): 65 | self.database = Database 66 | self.rankcard = Rank() 67 | 68 | 69 | utilities = Utilities() 70 | -------------------------------------------------------------------------------- /cogs/leveling.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from asyncio import sleep as asyncsleep 3 | from base.utilities import utilities 4 | from discord.ext import commands 5 | from random import randint 6 | from io import BytesIO 7 | from discord import File as dFile 8 | from discord import Member as dMember 9 | import aiohttp 10 | 11 | 12 | class Leveling(commands.Cog): 13 | def __init__(self, bot) -> None: 14 | self.bot = bot 15 | self.db = utilities.database(self.bot.loop, self.bot.cfg.postgresql_user, self.bot.cfg.postgresql_password) 16 | self.brake = [] 17 | 18 | @commands.Cog.listener() 19 | async def on_message(self, message) -> None: 20 | if message.author.id not in self.brake: 21 | if not await self.db.fetch(f'SELECT * FROM users WHERE id=\'{message.author.id}\''): 22 | await self.db.fetch(f'INSERT INTO users (id, rank, xp) VALUES (\'{message.author.id}\', \'0\', \'0\')') 23 | current_xp = 0 24 | 25 | else: 26 | result = await self.db.fetch(f'SELECT rank, xp FROM users WHERE id=\'{message.author.id}\'') 27 | current_xp = result[0][1] + randint(self.bot.cfg.min_message_xp, self.bot.cfg.max_message_xp) 28 | 29 | if current_xp >= utilities.rankcard.neededxp(result[0][0]): 30 | await self.db.fetch(f'UPDATE users SET rank=\'{result[0][0]+1}\', xp=\'0\' WHERE id=\'{message.author.id}\'') 31 | else: 32 | await self.db.fetch(f'UPDATE users SET xp=\'{current_xp}\' WHERE id=\'{message.author.id}\'') 33 | 34 | self.brake.append(message.author.id) 35 | await asyncsleep(randint(15, 25)) 36 | self.brake.remove(message.author.id) 37 | 38 | @commands.is_owner() 39 | @commands.command() 40 | async def tsql(self, ctx, *, sql: str) -> None: 41 | output = await self.db.fetch(sql) 42 | await ctx.send(f'```{output}```') 43 | 44 | @commands.command() 45 | async def rank(self, ctx, member: dMember=None) -> None: 46 | if member: 47 | uMember = member 48 | else: 49 | uMember = ctx.author 50 | result = await self.db.fetch(f'SELECT rank, xp FROM users WHERE id=\'{uMember.id}\'') 51 | if result: 52 | async with aiohttp.ClientSession() as session: 53 | async with session.get(f'{uMember.avatar_url}?size=128') as resp: 54 | profile_bytes = await resp.read() 55 | 56 | buffer = utilities.rankcard.draw(str(uMember), result[0][0], result[0][1], BytesIO(profile_bytes)) 57 | 58 | await ctx.send(file=dFile(fp=buffer, filename='rank_card.png')) 59 | else: 60 | await ctx.send(f'{uMember.mention}, you don\'t received xp yet.') 61 | 62 | 63 | def setup(bot) -> None: 64 | bot.add_cog(Leveling(bot)) 65 | --------------------------------------------------------------------------------