From c6daeb425e87f7ee930e65e1191ec9d63ef5efba Mon Sep 17 00:00:00 2001
From: Dan <daniel.sapelli@gmail.com>
Date: Mon, 1 Jul 2024 22:30:34 -0400
Subject: [PATCH] FIX: XP finally works/tracks (won't be earned on the site
 bot, just development)

---
 main.py            |  28 ++++++++++-
 modules/user/xp.py | 115 +++++++++++++++++++++++++++------------------
 2 files changed, 96 insertions(+), 47 deletions(-)

diff --git a/main.py b/main.py
index 54b7cf4..1fd80f0 100644
--- a/main.py
+++ b/main.py
@@ -8,71 +8,86 @@ TOKEN = config['DISCORD_TOKEN']
 GUILD_ID = config['GUILD_ID']
 
 intents = discord.Intents.default()
-intents.messages = True
+intents.message_content = True
 
 
 class Selena(discord.Client):
     def __init__(self):
         super().__init__(intents=intents)
         self.tree = discord.app_commands.CommandTree(self)
-        self.twitch = None
+        self.xp_module = None  # Initialize as None
         self.load_modules()
 
     async def setup_hook(self):
         logging.info("Setting up modules...")
         self.tree.copy_global_to(guild=discord.Object(id=GUILD_ID))
         await self.tree.sync(guild=discord.Object(id=GUILD_ID))
+        logging.info("Modules setup and commands synchronized")
+        # Call setup_hook for xp_module here
+        if self.xp_module:
+            await self.xp_module.setup_hook()
 
     def load_modules(self):
         if config['modules']['currency']['enabled']:
             from modules.user.currency import Currency
             currency = Currency(self)
             currency.setup(self.tree)
+            logging.info("Currency module loaded")
 
         if config['modules']['xp']['enabled']:
             from modules.user.xp import XP
             xp = XP(self)
             xp.setup(self.tree)
+            self.xp_module = xp  # Set the xp_module attribute
+            logging.info("XP module loaded")
 
         if config['modules']['birthday']['enabled']:
             from modules.user.birthday import Birthday
             birthday = Birthday(self)
             birthday.setup(self.tree)
+            logging.info("Birthday module loaded")
 
         if config['modules']['destiny2']['enabled']:
             from modules.games.destiny2 import Destiny2
             destiny2 = Destiny2(self)
             destiny2.setup(self.tree)
+            logging.info("Destiny 2 module loaded")
 
         if config['modules']['music']['enabled']:
             from modules.music.music import Music
             music = Music(self)
             music.setup(self.tree)
+            logging.info("Music module loaded")
 
         if config['modules']['youtube']['enabled']:
             from modules.social.youtube import YouTube
             youtube = YouTube(self)
             youtube.setup(self.tree)
+            logging.info("YouTube module loaded")
 
         if config['modules']['twitch']['enabled']:
             from modules.social.twitch import Twitch
             twitch = Twitch(self)
             twitch.setup(self.tree)
+            logging.info("Twitch module loaded")
 
         if config['modules']['update']['enabled']:
             from modules.admin.update import Update
             update = Update(self)
             update.setup(self.tree)
+            logging.info("Update module loaded")
 
         if config['modules']['data_privacy']['enabled']:
             from modules.admin.data_privacy import DataPrivacy
             data_privacy = DataPrivacy(self)
             data_privacy.setup(self.tree)
+            logging.info("Data Privacy module loaded")
 
         if config['modules']['terms_privacy']['enabled']:
             from modules.admin.terms_privacy import TermsPrivacy
             terms_privacy = TermsPrivacy(self)
             terms_privacy.setup(self.tree)
+            logging.info("Terms and Privacy module loaded")
 
 
 bot = Selena()
@@ -82,4 +97,13 @@ bot = Selena()
 async def on_ready():
     logging.info(f'{bot.user.name} has connected to Discord!')
 
+
+@bot.event
+async def on_message(message):
+    logging.debug(f"Message from {message.author}: {message.content}")
+    if message.author == bot.user:
+        return
+    if bot.xp_module:
+        await bot.xp_module.handle_message(message)
+
 bot.run(TOKEN)
diff --git a/modules/user/xp.py b/modules/user/xp.py
index 689ac4a..7d03860 100644
--- a/modules/user/xp.py
+++ b/modules/user/xp.py
@@ -1,72 +1,97 @@
-# xp.py
 import discord
-from discord import app_commands
 import sqlite3
 import random
+import logging
+import asyncio
 
 
 class XP:
     def __init__(self, bot):
         self.bot = bot
         self.db_path = 'data/selena.db'
+        self.logger = logging.getLogger('XP')
+        self.logger.setLevel(logging.DEBUG)
+        handler = logging.FileHandler(filename='log/selena.log', encoding='utf-8', mode='w')
+        handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s:%(message)s'))
+        self.logger.addHandler(handler)
+        self.cooldown = 10  # Cooldown time in seconds
+        self.xp_range = (5, 15)  # Range of XP that can be earned per message
+        self.user_cooldowns = {}
 
-    def calculate_level(self, xp):
-        level = 1
-        while xp >= 5 * (level ** 2) + 50 * level + 100:
-            level += 1
-        return level
-
-    async def add_xp(self, interaction: discord.Interaction):
-        guild_id = str(interaction.guild_id)
-        user_id = str(interaction.user.id)
-        earned_xp = random.randint(5, 15)
+        self.ensure_table_exists()
 
+    def ensure_table_exists(self):
         conn = sqlite3.connect(self.db_path)
         cursor = conn.cursor()
         cursor.execute("""
-            INSERT INTO guild_xp (guild_id, user_id, xp, level) 
-            VALUES (?, ?, ?, 1) 
-            ON CONFLICT(guild_id, user_id) 
-            DO UPDATE SET xp = xp + ?
-        """, (guild_id, user_id, earned_xp, earned_xp))
-        conn.commit()
-        cursor.execute("SELECT xp, level FROM guild_xp WHERE guild_id = ? AND user_id = ?", (guild_id, user_id))
-        xp, level = cursor.fetchone()
-        new_level = self.calculate_level(xp)
-        if new_level > level:
-            cursor.execute("UPDATE guild_xp SET level = ? WHERE guild_id = ? AND user_id = ?", (new_level, guild_id, user_id))
+        CREATE TABLE IF NOT EXISTS xp (
+            guild_id TEXT NOT NULL,
+            user_id TEXT NOT NULL,
+            xp INTEGER NOT NULL,
+            PRIMARY KEY (guild_id, user_id)
+        );
+        """)
         conn.commit()
         conn.close()
+        self.logger.info('XP table ensured in database')
 
-        await interaction.response.send_message(f"You earned {earned_xp} XP! You now have {xp} XP and are level {new_level}.", ephemeral=False)
-
-    async def check_xp(self, interaction: discord.Interaction):
-        guild_id = str(interaction.guild_id)
-        user_id = str(interaction.user.id)
-
+    async def add_xp(self, guild_id, user_id, xp):
         conn = sqlite3.connect(self.db_path)
         cursor = conn.cursor()
-        cursor.execute("SELECT xp, level FROM guild_xp WHERE guild_id = ? AND user_id = ?", (guild_id, user_id))
-        result = cursor.fetchone()
+        cursor.execute("""
+            INSERT INTO xp (guild_id, user_id, xp)
+            VALUES (?, ?, ?)
+            ON CONFLICT(guild_id, user_id)
+            DO UPDATE SET xp = xp + excluded.xp
+        """, (guild_id, user_id, xp))
+        conn.commit()
         conn.close()
+        self.logger.debug(f'Added {xp} XP to user {user_id} in guild {guild_id}')
 
-        if result:
-            xp, level = result
-            await interaction.response.send_message(f"You have {xp} XP and are level {level}.", ephemeral=False)
-        else:
-            await interaction.response.send_message("You don't have any XP yet.", ephemeral=False)
+    async def get_xp(self, guild_id, user_id):
+        conn = sqlite3.connect(self.db_path)
+        cursor = conn.cursor()
+        cursor.execute("SELECT xp FROM xp WHERE guild_id = ? AND user_id = ?", (guild_id, user_id))
+        row = cursor.fetchone()
+        conn.close()
+        return row[0] if row else 0
 
-    def setup(self, tree: app_commands.CommandTree):
-        @tree.command(name="earn_xp", description="Earn XP")
-        async def earn_xp_command(interaction: discord.Interaction):
-            await self.add_xp(interaction)
+    async def handle_message(self, message):
+        self.logger.debug(f'Received message from user {message.author.id} in guild {message.guild.id}')
 
-        @tree.command(name="check_xp", description="Check your XP and level")
+        if message.author.bot:
+            self.logger.debug('Message author is a bot, ignoring.')
+            return
+
+        guild_id = str(message.guild.id)
+        user_id = str(message.author.id)
+
+        if user_id in self.user_cooldowns and self.user_cooldowns[user_id] > asyncio.get_event_loop().time():
+            self.logger.debug(f'User {user_id} is on cooldown')
+            return
+
+        xp = random.randint(*self.xp_range)
+        await self.add_xp(guild_id, user_id, xp)
+        self.user_cooldowns[user_id] = asyncio.get_event_loop().time() + self.cooldown
+        self.logger.info(f'Added {xp} XP to user {user_id} in guild {guild_id}')
+
+    def setup(self, tree: discord.app_commands.CommandTree):
+        @tree.command(name="check_xp", description="Check your XP")
         async def check_xp_command(interaction: discord.Interaction):
-            await self.check_xp(interaction)
-
-        if not tree.get_command("earn_xp"):
-            tree.add_command(earn_xp_command)
+            user_id = str(interaction.user.id)
+            guild_id = str(interaction.guild.id)
+            xp = await self.get_xp(guild_id, user_id)
+            await interaction.response.send_message(embed=discord.Embed(description=f"You have {xp} XP.", color=discord.Color.green()))
 
         if not tree.get_command("check_xp"):
             tree.add_command(check_xp_command)
+
+    async def setup_hook(self):
+        self.bot.event(self.handle_message)
+        self.logger.info('XP module setup complete and listener added')
+
+
+def setup(bot):
+    xp = XP(bot)
+    xp.setup(bot.tree)
+    bot.loop.create_task(xp.setup_hook())