diff --git a/config.py b/config.py index 55b0385..0bb5a6b 100644 --- a/config.py +++ b/config.py @@ -9,6 +9,9 @@ config = { 'modules': { 'currency': { 'enabled': True + }, + 'xp': { + 'enabled': True } } } diff --git a/main.py b/main.py index fc4523f..af5c8da 100644 --- a/main.py +++ b/main.py @@ -20,9 +20,16 @@ class Selena(discord.Client): currency = Currency(self) currency.setup(self.tree) + if config['modules']['xp']['enabled']: + from modules.user.xp import XP + xp = XP(self) + xp.setup(self.tree) + async def setup_hook(self): self.tree.copy_global_to(guild=discord.Object(id=GUILD_ID)) await self.tree.sync(guild=discord.Object(id=GUILD_ID)) + # Force sync for all commands + await self.tree.sync() bot = Selena() diff --git a/modules/user/currency.py b/modules/user/currency.py index efe4476..68e3b7b 100644 --- a/modules/user/currency.py +++ b/modules/user/currency.py @@ -2,7 +2,7 @@ import discord from discord import app_commands import random import sqlite3 -import os +from datetime import datetime, timedelta class Currency: @@ -10,6 +10,7 @@ class Currency: self.bot = bot self.db_path = 'data/selena.db' self._init_db() + self.cooldowns = {} def _init_db(self): with sqlite3.connect(self.db_path) as conn: @@ -17,7 +18,9 @@ class Currency: cursor.execute(''' CREATE TABLE IF NOT EXISTS users ( user_id TEXT PRIMARY KEY, - kibble INTEGER DEFAULT 0 + kibble INTEGER DEFAULT 0, + xp INTEGER DEFAULT 0, + level INTEGER DEFAULT 1 ) ''') conn.commit() @@ -25,38 +28,51 @@ class Currency: def _get_user_kibble(self, user_id): with sqlite3.connect(self.db_path) as conn: cursor = conn.cursor() - cursor.execute('SELECT kibble FROM users WHERE user_id = ?', - (user_id,)) + cursor.execute('SELECT kibble FROM users WHERE user_id = ?', (user_id,)) row = cursor.fetchone() return row[0] if row else 0 def _update_user_kibble(self, user_id, amount): with sqlite3.connect(self.db_path) as conn: cursor = conn.cursor() - cursor.execute('INSERT OR IGNORE INTO users (user_id, kibble) VALUES (?, ?)', - (user_id, 0)) - cursor.execute('UPDATE users SET kibble = kibble + ? WHERE user_id = ?', - (amount, user_id)) + cursor.execute('INSERT OR IGNORE INTO users (user_id, kibble) VALUES (?, ?)', (user_id, 0)) + cursor.execute('UPDATE users SET kibble = kibble + ? WHERE user_id = ?', (amount, user_id)) conn.commit() - def setup(self, tree): - @tree.command(name='earn_kibble', description='Earn Kibble') - @app_commands.checks.cooldown(1, 30) - async def earn_kibble(interaction: discord.Interaction): - user_id = str(interaction.user.id) - amount = random.choices([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100], - [0.01, 10, 10, 10, 10, 10, 10, 10, 10, - 10, 10, 0.01])[0] - self._update_user_kibble(user_id, amount) - await interaction.response.send_message - (f'{interaction.user.mention} earned {amount} Kibble!') + async def earn_kibble(self, interaction: discord.Interaction): + user_id = str(interaction.user.id) + now = datetime.utcnow() - @tree.command(name='balance', description='Check your Kibble balance') - async def balance(interaction: discord.Interaction): - user_id = str(interaction.user.id) - balance = self._get_user_kibble(user_id) - await interaction.response.send_message - (f'{interaction.user.mention} has {balance} Kibble.') + if user_id in self.cooldowns: + if now < self.cooldowns[user_id]: + retry_after = (self.cooldowns[user_id] - now).total_seconds() + await interaction.response.send_message( + f'{interaction.user.mention}, you are on a cooldown. Please wait {retry_after:.2f} seconds before using this command again.', + ephemeral=True + ) + return + + self.cooldowns[user_id] = now + timedelta(seconds=30) + amount = random.choices([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100], [0.01, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0.01])[0] + self._update_user_kibble(user_id, amount) + await interaction.response.send_message(f'{interaction.user.mention} earned {amount} Kibble!') + + async def balance(self, interaction: discord.Interaction): + user_id = str(interaction.user.id) + balance = self._get_user_kibble(user_id) + await interaction.response.send_message(f'{interaction.user.mention} has {balance} Kibble.') + + def setup(self, tree): + tree.add_command(app_commands.Command( + name='earn_kibble', + description='Earn Kibble', + callback=self.earn_kibble + )) + tree.add_command(app_commands.Command( + name='balance', + description='Check your Kibble balance', + callback=self.balance + )) def setup(bot): diff --git a/modules/user/xp.py b/modules/user/xp.py new file mode 100644 index 0000000..512865b --- /dev/null +++ b/modules/user/xp.py @@ -0,0 +1,107 @@ +import discord +from discord import app_commands +from datetime import datetime, timedelta +import sqlite3 + + +class XP: + def __init__(self, bot): + self.bot = bot + self.db_path = 'data/selena.db' + self._init_db() + self.cooldowns = {} + + def _init_db(self): + with sqlite3.connect(self.db_path) as conn: + cursor = conn.cursor() + cursor.execute(''' + CREATE TABLE IF NOT EXISTS users ( + user_id TEXT PRIMARY KEY, + kibble INTEGER DEFAULT 0, + xp INTEGER DEFAULT 0, + level INTEGER DEFAULT 1 + ) + ''') + conn.commit() + + def _get_user_xp(self, user_id): + with sqlite3.connect(self.db_path) as conn: + cursor = conn.cursor() + cursor.execute('SELECT xp, level FROM users WHERE user_id = ?', (user_id,)) + row = cursor.fetchone() + return row if row else (0, 1) + + def _update_user_xp(self, user_id, xp): + with sqlite3.connect(self.db_path) as conn: + cursor = conn.cursor() + cursor.execute('INSERT OR IGNORE INTO users (user_id, xp, level) VALUES (?, ?, ?)', (user_id, 0, 1)) + cursor.execute('UPDATE users SET xp = xp + ? WHERE user_id = ?', (xp, user_id)) + conn.commit() + + def _update_user_level(self, user_id, level): + with sqlite3.connect(self.db_path) as conn: + cursor = conn.cursor() + cursor.execute('UPDATE users SET level = ? WHERE user_id = ?', (level, user_id)) + conn.commit() + + def xp_needed_for_next_level(self, level): + return 5 * (level ** 2) + 50 * level + 100 + + async def on_message(self, message): + if message.author.bot: + return + + user_id = str(message.author.id) + now = datetime.now() + + if user_id in self.cooldowns: + if now < self.cooldowns[user_id]: + return + + self.cooldowns[user_id] = now + timedelta(seconds=10) + + xp, level = self._get_user_xp(user_id) + self._update_user_xp(user_id, 10) + xp += 10 + + if xp >= self.xp_needed_for_next_level(level): + level += 1 + self._update_user_level(user_id, level) + await message.channel.send(f'{message.author.mention} has leveled up to level {level}!') + + @app_commands.command(name='earn_xp', description='Manually earn XP (for testing)') + @app_commands.checks.cooldown(1, 10, key=lambda i: (i.user.id)) + async def earn_xp(self, interaction: discord.Interaction): + user_id = str(interaction.user.id) + xp, level = self._get_user_xp(user_id) + self._update_user_xp(user_id, 10) + xp += 10 + + if xp >= self.xp_needed_for_next_level(level): + level += 1 + self._update_user_level(user_id, level) + await interaction.response.send_message(f'{interaction.user.mention} has leveled up to level {level}!') + else: + await interaction.response.send_message(f'{interaction.user.mention} earned 10 XP!') + + @earn_xp.error + async def earn_xp_error(self, interaction: discord.Interaction, error: app_commands.AppCommandError): + if isinstance(error, app_commands.CommandOnCooldown): + await interaction.response.send_message(f'{interaction.user.mention}, you are on cooldown. Try again in {error.retry_after:.2f} seconds.', ephemeral=True) + + @app_commands.command(name='check_progress', description='Check your XP progress towards the next level') + async def check_progress(self, interaction: discord.Interaction): + user_id = str(interaction.user.id) + xp, level = self._get_user_xp(user_id) + xp_needed = self.xp_needed_for_next_level(level) + xp_to_next_level = xp_needed - xp + + await interaction.response.send_message(f'{interaction.user.mention}, you need {xp_to_next_level} more XP to reach the next level.') + + def setup(self, tree): + tree.add_command(self.earn_xp) + tree.add_command(self.check_progress) + + +def setup(bot): + bot.tree.add_cog(XP(bot))