diff --git a/config.py b/config.py index a613530..634f074 100644 --- a/config.py +++ b/config.py @@ -1,4 +1,5 @@ import os + from dotenv import load_dotenv load_dotenv() diff --git a/main.py b/main.py index c2d2ba2..fe2228c 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,9 @@ -import discord -import config import logging +import discord + +import config + # Set up logging logging.basicConfig(level=logging.DEBUG) @@ -37,11 +39,14 @@ class Selena(discord.Client): await self.load_extension(extension) async def on_ready(self): - print(f'Logged in as {self.user} (ID: {self.user.id})') - print('------') + print(f"Logged in as {self.user} (ID: {self.user.id})") + print("------") if __name__ == "__main__": + # Enable message content intent intents = discord.Intents.default() + intents.message_content = True + client = Selena(intents=intents) client.run(config.DISCORD_TOKEN) diff --git a/modules/admin/logger_module.py b/modules/admin/logger_module.py index 8c3ec40..c2ec191 100644 --- a/modules/admin/logger_module.py +++ b/modules/admin/logger_module.py @@ -1,7 +1,10 @@ # modules/admin/logger_module.py -import discord import logging import logging.config + +import discord +from discord import app_commands + from .logging_config import logging_config @@ -10,20 +13,52 @@ class LoggerModule: self.bot = bot logging.config.dictConfig(logging_config) self.logger = logging.getLogger(__name__) + self.add_logging_commands() def add_logging_commands(self): - @self.bot.tree.command(name="log_test", - description="Test the logging system") + @self.bot.tree.command(name="log_test", description="Test the logging system") # noqa: E501 async def log_test(interaction: discord.Interaction): self.logger.debug("This is a debug message") self.logger.info("This is an info message") self.logger.warning("This is a warning message") self.logger.error("This is an error message") self.logger.critical("This is a critical message") - await interaction.response.send_message("Logging test completed." - "Check the logs!") + await interaction.response.send_message( + "Logging test completed. Check the logs!" + ) + + @self.bot.tree.command( + name="set_log_level", description="Set the logging level (Owner/Admin only)" # noqa: E501 + ) + @app_commands.choices( + level=[ + app_commands.Choice(name="DEBUG", value="DEBUG"), + app_commands.Choice(name="INFO", value="INFO"), + app_commands.Choice(name="WARNING", value="WARNING"), + app_commands.Choice(name="ERROR", value="ERROR"), + app_commands.Choice(name="CRITICAL", value="CRITICAL"), + ] + ) + async def set_log_level( + interaction: discord.Interaction, level: app_commands.Choice[str] + ): + guild = interaction.guild + if guild is not None and ( + interaction.user.id == guild.owner_id + or any( + role.permissions.administrator for role in interaction.user.roles # noqa: E501 + ) + ): + logging.getLogger().setLevel(level.value) + await interaction.response.send_message( + f"Logging level set to {level.value}" + ) + else: + await interaction.response.send_message( + "You do not have permission to set the logging level.", + ephemeral=True, + ) async def setup(bot): - logger_module = LoggerModule(bot) - logger_module.add_logging_commands() + LoggerModule(bot) diff --git a/modules/admin/logging_config.py b/modules/admin/logging_config.py index 77f1496..7d65010 100644 --- a/modules/admin/logging_config.py +++ b/modules/admin/logging_config.py @@ -1,7 +1,5 @@ # modules/admin/logging_config.py -import logging -import logging.handlers import os LOG_DIR = "logs" @@ -14,9 +12,7 @@ logging_config = { "version": 1, "disable_existing_loggers": False, "formatters": { - "standard": { - "format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s" - }, + "standard": {"format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s"}, }, "handlers": { "console": { @@ -29,7 +25,7 @@ logging_config = { "class": "logging.handlers.RotatingFileHandler", "formatter": "standard", "filename": LOG_FILE, - "maxBytes": 1024*1024*5, # 5 MB + "maxBytes": 1024 * 1024 * 5, # 5 MB "backupCount": 3, }, }, diff --git a/modules/admin/policy_module.py b/modules/admin/policy_module.py index 99c0360..e97573a 100644 --- a/modules/admin/policy_module.py +++ b/modules/admin/policy_module.py @@ -1,6 +1,7 @@ +import logging + import discord from discord import app_commands -import logging logger = logging.getLogger(__name__) @@ -47,7 +48,7 @@ class PolicyModule: embed=discord.Embed( title="Privacy Policy", description=privacy_policy_text, - color=discord.Color.blue() + color=discord.Color.blue(), ) ) logger.info(f"User {interaction.user.id} viewed the privacy policy") @@ -93,7 +94,7 @@ class PolicyModule: embed=discord.Embed( title="Terms of Service", description=tos_text, - color=discord.Color.blue() + color=discord.Color.blue(), ) ) logger.info(f"User {interaction.user.id} viewed the terms of service") diff --git a/modules/data/db.py b/modules/data/db.py index 3d405b4..80138ce 100644 --- a/modules/data/db.py +++ b/modules/data/db.py @@ -2,56 +2,68 @@ import sqlite3 def initialize_db(): - conn = sqlite3.connect('selena.db') + conn = sqlite3.connect("selena.db") cursor = conn.cursor() # Birthdays table - cursor.execute(''' + cursor.execute( + """ CREATE TABLE IF NOT EXISTS birthdays ( user_id TEXT PRIMARY KEY, birthday TEXT ) - ''') + """ + ) # Currency table - cursor.execute(''' + cursor.execute( + """ CREATE TABLE IF NOT EXISTS currency ( user_id TEXT PRIMARY KEY, balance INTEGER, last_earned TIMESTAMP ) - ''') + """ + ) # Followed channels table - cursor.execute(''' + cursor.execute( + """ CREATE TABLE IF NOT EXISTS followed_channels ( twitch_name TEXT PRIMARY KEY, discord_channel_id INTEGER ) - ''') - cursor.execute(''' + """ + ) + cursor.execute( + """ CREATE TABLE IF NOT EXISTS followed_youtube_channels ( youtube_channel_id TEXT PRIMARY KEY, discord_channel_id INTEGER ) - ''') - cursor.execute(''' + """ + ) + cursor.execute( + """ CREATE TABLE IF NOT EXISTS youtube_status ( youtube_channel_id TEXT PRIMARY KEY, last_video_id TEXT ) - ''') - cursor.execute(''' + """ + ) + cursor.execute( + """ CREATE TABLE IF NOT EXISTS user_xp ( user_id INTEGER PRIMARY KEY, xp INTEGER, level INTEGER ) - ''') + """ + ) conn.commit() conn.close() def get_connection(): - return sqlite3.connect('selena.db') + return sqlite3.connect("selena.db") diff --git a/modules/media/spotify_module.py b/modules/media/spotify_module.py index 33a5646..bbef10c 100644 --- a/modules/media/spotify_module.py +++ b/modules/media/spotify_module.py @@ -1,8 +1,10 @@ import logging + import discord -from discord import app_commands import spotipy +from discord import app_commands from spotipy.oauth2 import SpotifyOAuth + import config # Set up logging @@ -21,7 +23,7 @@ class SpotifyModule: scope=( "user-library-read user-read-playback-state " "user-modify-playback-state user-read-currently-playing" - ) + ), ) ) self.add_commands() @@ -39,7 +41,7 @@ class SpotifyModule: embed=discord.Embed( title="Current Track", description="No song is currently playing", - color=discord.Color.red() + color=discord.Color.red(), ) ) logger.info("No song is currently playing") @@ -49,15 +51,16 @@ class SpotifyModule: embed = discord.Embed( title="Current Track", description=f"{track['name']} by {artist}", - color=discord.Color.green() + color=discord.Color.green(), ) embed.add_field( - name="Album", value=track['album']['name'], - inline=False + name="Album", value=track["album"]["name"], inline=False ) - embed.set_thumbnail(url=track['album']['images'][0]['url']) + embed.set_thumbnail(url=track["album"]["images"][0]["url"]) await interaction.followup.send(embed=embed) - logger.info(f"Currently playing: {track['name']} by {artist}") # noqa: E501 + logger.info( + f"Currently playing: {track['name']} by {artist}" + ) # noqa: E501 except Exception as e: await interaction.followup.send(f"An error occurred: {e}") logger.error(f"Error in current_track command: {e}") @@ -74,7 +77,7 @@ class SpotifyModule: embed=discord.Embed( title="Play Track", description="No results found", - color=discord.Color.red() + color=discord.Color.red(), ) ) logger.info(f"No results found for query: {query}") @@ -90,7 +93,7 @@ class SpotifyModule: title="Play Track", description="No active devices found." "Please open Spotify on a device.", - color=discord.Color.red() + color=discord.Color.red(), ) ) logger.info("No active devices found for playback") @@ -100,22 +103,23 @@ class SpotifyModule: embed = discord.Embed( title="Now Playing", description=f"{track['name']} by {', '.join([a['name'] for a in track['artists']])}", # noqa: E501 - color=discord.Color.green() + color=discord.Color.green(), ) embed.add_field( - name="Album", value=track['album']['name'], inline=False + name="Album", value=track["album"]["name"], inline=False ) - embed.set_thumbnail(url=track['album']['images'][0]['url']) + embed.set_thumbnail(url=track["album"]["images"][0]["url"]) await interaction.followup.send(embed=embed) - logger.info(f"Now playing: {track['name']} by {', '.join([a['name'] for a in track['artists']])}") # noqa: E501 + logger.info( + f"Now playing: {track['name']} by {', '.join([a['name'] for a in track['artists']])}" + ) # noqa: E501 except Exception as e: await interaction.followup.send(f"An error occurred: {e}") logger.error(f"Error in play_track command: {e}") @app_commands.command( name="play_playlist", - description="Play a playlist by searching for it" - "or providing a link" + description="Play a playlist by searching for it" "or providing a link", ) async def play_playlist(interaction: discord.Interaction, query: str): await interaction.response.defer() @@ -132,7 +136,7 @@ class SpotifyModule: embed=discord.Embed( title="Play Playlist", description="No results found", - color=discord.Color.red() + color=discord.Color.red(), ) ) logger.info(f"No results found for query: {query}") @@ -147,7 +151,7 @@ class SpotifyModule: title="Play Playlist", description="No active devices found." "Please open Spotify on a device.", - color=discord.Color.red() + color=discord.Color.red(), ) ) logger.info("No active devices found for playback") @@ -156,13 +160,19 @@ class SpotifyModule: self.sp.start_playback(context_uri=uri) embed = discord.Embed( title="Now Playing Playlist", - description=f"{playlist['name']} by {playlist['owner']['display_name']}" if not query.startswith("https://open.spotify.com/playlist/") else "Playing playlist", # noqa: E501 - color=discord.Color.green() + description=( + f"{playlist['name']} by {playlist['owner']['display_name']}" + if not query.startswith("https://open.spotify.com/playlist/") + else "Playing playlist" + ), # noqa: E501 + color=discord.Color.green(), ) if not query.startswith("https://open.spotify.com/playlist/"): - embed.set_thumbnail(url=playlist['images'][0]['url']) + embed.set_thumbnail(url=playlist["images"][0]["url"]) await interaction.followup.send(embed=embed) - logger.info(f"Now playing playlist: {playlist['name']} by {playlist['owner']['display_name']}") # noqa: E501 + logger.info( + f"Now playing playlist: {playlist['name']} by {playlist['owner']['display_name']}" + ) # noqa: E501 except Exception as e: await interaction.followup.send(f"An error occurred: {e}") logger.error(f"Error in play_playlist command: {e}") @@ -178,7 +188,7 @@ class SpotifyModule: embed=discord.Embed( title="Pause", description="Playback paused.", - color=discord.Color.green() + color=discord.Color.green(), ) ) logger.info("Playback paused") @@ -197,7 +207,7 @@ class SpotifyModule: embed=discord.Embed( title="Resume", description="Playback resumed.", - color=discord.Color.green() + color=discord.Color.green(), ) ) logger.info("Playback resumed") @@ -205,9 +215,7 @@ class SpotifyModule: await interaction.followup.send(f"An error occurred: {e}") logger.error(f"Error in resume command: {e}") - @app_commands.command( - name="next", description="Skip to the next track" - ) + @app_commands.command(name="next", description="Skip to the next track") async def next_track(interaction: discord.Interaction): await interaction.response.defer() try: @@ -216,7 +224,7 @@ class SpotifyModule: embed=discord.Embed( title="Next Track", description="Skipped to the next track.", - color=discord.Color.green() + color=discord.Color.green(), ) ) logger.info("Skipped to the next track") @@ -235,7 +243,7 @@ class SpotifyModule: embed=discord.Embed( title="Previous Track", description="Returned to the previous track.", - color=discord.Color.green() + color=discord.Color.green(), ) ) logger.info("Returned to the previous track") diff --git a/modules/money/currency_module.py b/modules/money/currency_module.py index 279a5d3..2e78844 100644 --- a/modules/money/currency_module.py +++ b/modules/money/currency_module.py @@ -1,11 +1,12 @@ # Flake8:noqa: E501 -import discord -from discord import app_commands -import sqlite3 import logging import random from datetime import datetime, timedelta -from modules.data.db import initialize_db, get_connection + +import discord +from discord import app_commands + +from modules.data.db import get_connection, initialize_db logger = logging.getLogger(__name__) initialize_db() @@ -19,18 +20,18 @@ class CurrencyModule: self.add_commands() def add_commands(self): - @app_commands.command( - name="earn_kibble", description="Earn Kibble" - ) + @app_commands.command(name="earn_kibble", description="Earn Kibble") async def earn_kibble(interaction: discord.Interaction): await interaction.response.defer() try: conn = get_connection() cursor = conn.cursor() user_id = str(interaction.user.id) - + # Check cooldown - cursor.execute('SELECT last_earned FROM currency WHERE user_id = ?', (user_id,)) + cursor.execute( + "SELECT last_earned FROM currency WHERE user_id = ?", (user_id,) + ) result = cursor.fetchone() if result and result[0]: last_earned = datetime.fromisoformat(result[0]) @@ -39,30 +40,40 @@ class CurrencyModule: embed=discord.Embed( title="Earn Kibble", description="You are on cooldown. Please try again later.", - color=discord.Color.red() + color=discord.Color.red(), ) ) conn.close() - logger.info(f"User {user_id} attempted to earn Kibble but is on cooldown.") + logger.info( + f"User {user_id} attempted to earn Kibble but is on cooldown." + ) return amount = random.choices([random.randint(1, 10), 100], [0.99, 0.01])[0] - cursor.execute('INSERT OR IGNORE INTO currency (user_id, balance, last_earned) VALUES (?, ?, ?)', - (user_id, 0, datetime.now().isoformat())) - cursor.execute('UPDATE currency SET balance = balance + ?, last_earned = ? WHERE user_id = ?', - (amount, datetime.now().isoformat(), user_id)) + cursor.execute( + "INSERT OR IGNORE INTO currency (user_id, balance, last_earned) VALUES (?, ?, ?)", + (user_id, 0, datetime.now().isoformat()), + ) + cursor.execute( + "UPDATE currency SET balance = balance + ?, last_earned = ? WHERE user_id = ?", + (amount, datetime.now().isoformat(), user_id), + ) conn.commit() - cursor.execute('SELECT balance FROM currency WHERE user_id = ?', (user_id,)) + cursor.execute( + "SELECT balance FROM currency WHERE user_id = ?", (user_id,) + ) new_balance = cursor.fetchone()[0] conn.close() await interaction.followup.send( embed=discord.Embed( title="Earn Kibble", description=f"You have earned {amount} Kibble. Your new balance is {new_balance}.", - color=discord.Color.green() + color=discord.Color.green(), ) ) - logger.info(f"User {user_id} earned {amount} Kibble. New balance: {new_balance}") + logger.info( + f"User {user_id} earned {amount} Kibble. New balance: {new_balance}" + ) except Exception as e: await interaction.followup.send(f"An error occurred: {e}") logger.error(f"Error in earn_kibble command: {e}") @@ -75,7 +86,10 @@ class CurrencyModule: try: conn = get_connection() cursor = conn.cursor() - cursor.execute('SELECT balance FROM currency WHERE user_id = ?', (str(interaction.user.id),)) + cursor.execute( + "SELECT balance FROM currency WHERE user_id = ?", + (str(interaction.user.id),), + ) result = cursor.fetchone() conn.close() if result: @@ -83,16 +97,18 @@ class CurrencyModule: embed=discord.Embed( title="Check Balance", description=f"Your current balance is {result[0]} Kibble.", - color=discord.Color.green() + color=discord.Color.green(), ) ) - logger.info(f"User {interaction.user.id} checked balance: {result[0]}") + logger.info( + f"User {interaction.user.id} checked balance: {result[0]}" + ) else: await interaction.followup.send( embed=discord.Embed( title="Check Balance", description="You have no Kibble.", - color=discord.Color.red() + color=discord.Color.red(), ) ) logger.info(f"User {interaction.user.id} has no Kibble.") diff --git a/modules/social/twitch_module.py b/modules/social/twitch_module.py index 23650be..23207cb 100644 --- a/modules/social/twitch_module.py +++ b/modules/social/twitch_module.py @@ -1,10 +1,12 @@ +import asyncio +import logging + import discord from discord import app_commands -from twitchAPI.twitch import Twitch from twitchAPI.helper import first -import logging +from twitchAPI.twitch import Twitch + import config -import asyncio from modules.data.db import get_connection logger = logging.getLogger(__name__) @@ -13,8 +15,7 @@ logger = logging.getLogger(__name__) class TwitchModule: def __init__(self, bot): self.bot = bot - self.twitch = Twitch(config.TWITCH_CLIENT_ID, - config.TWITCH_CLIENT_SECRET) + self.twitch = Twitch(config.TWITCH_CLIENT_ID, config.TWITCH_CLIENT_SECRET) self.bot.loop.create_task(self.authenticate_twitch()) self.bot.loop.create_task(self.check_live_streams()) self.add_commands() @@ -26,15 +27,17 @@ class TwitchModule: while True: conn = get_connection() c = conn.cursor() - c.execute(''' + c.execute( + """ CREATE TABLE IF NOT EXISTS live_status ( twitch_name TEXT PRIMARY KEY, is_live BOOLEAN ) - ''') + """ + ) conn.commit() - c.execute('SELECT twitch_name, discord_channel_id FROM followed_channels') + c.execute("SELECT twitch_name, discord_channel_id FROM followed_channels") followed_channels = c.fetchall() for twitch_name, discord_channel_id in followed_channels: @@ -47,7 +50,10 @@ class TwitchModule: streams = await first(self.twitch.get_streams(user_id=[user_id])) is_live = streams is not None - c.execute('SELECT is_live FROM live_status WHERE twitch_name = ?', (twitch_name,)) + c.execute( + "SELECT is_live FROM live_status WHERE twitch_name = ?", + (twitch_name,), + ) row = c.fetchone() was_live = row[0] if row else False @@ -61,18 +67,26 @@ class TwitchModule: f"**Game:** {streams.game_name}\n" f"**Viewers:** {streams.viewer_count}" ), - color=discord.Color.green() + color=discord.Color.green(), ) embed.set_thumbnail( - url=streams.thumbnail_url.replace('{width}', '320').replace('{height}', '180') + url=streams.thumbnail_url.replace( + "{width}", "320" + ).replace("{height}", "180") ) await channel.send(embed=embed) - c.execute('INSERT OR REPLACE INTO live_status (twitch_name, is_live) VALUES (?, ?)', (twitch_name, is_live)) + c.execute( + "INSERT OR REPLACE INTO live_status (twitch_name, is_live) VALUES (?, ?)", + (twitch_name, is_live), + ) conn.commit() except Exception as e: - logger.error(f"Error checking live status for {twitch_name}: {e}", exc_info=True) + logger.error( + f"Error checking live status for {twitch_name}: {e}", + exc_info=True, + ) conn.close() await asyncio.sleep(300) # Check every 5 minutes @@ -80,15 +94,19 @@ class TwitchModule: def add_commands(self): @app_commands.command( name="follow_twitch", - description="Follow a Twitch channel to get live alerts" + description="Follow a Twitch channel to get live alerts", ) - async def follow_twitch(interaction: discord.Interaction, twitch_name: str, channel: discord.TextChannel = None): + async def follow_twitch( + interaction: discord.Interaction, + twitch_name: str, + channel: discord.TextChannel = None, + ): channel = channel or interaction.channel conn = get_connection() c = conn.cursor() c.execute( - 'INSERT OR REPLACE INTO followed_channels (twitch_name, discord_channel_id) VALUES (?, ?)', - (twitch_name, channel.id) + "INSERT OR REPLACE INTO followed_channels (twitch_name, discord_channel_id) VALUES (?, ?)", + (twitch_name, channel.id), ) conn.commit() conn.close() @@ -96,40 +114,34 @@ class TwitchModule: embed=discord.Embed( title="Followed Twitch Channel", description=f"Now following {twitch_name}. Alerts will be sent to {channel.mention}.", - color=discord.Color.green() + color=discord.Color.green(), ) ) logger.info(f"Now following {twitch_name} for alerts in {channel.name}") @app_commands.command( - name="unfollow_twitch", - description="Unfollow a Twitch channel" + name="unfollow_twitch", description="Unfollow a Twitch channel" ) async def unfollow_twitch(interaction: discord.Interaction, twitch_name: str): conn = get_connection() c = conn.cursor() c.execute( - 'DELETE FROM followed_channels WHERE twitch_name = ?', - (twitch_name,) - ) - c.execute( - 'DELETE FROM live_status WHERE twitch_name = ?', - (twitch_name,) + "DELETE FROM followed_channels WHERE twitch_name = ?", (twitch_name,) ) + c.execute("DELETE FROM live_status WHERE twitch_name = ?", (twitch_name,)) conn.commit() conn.close() await interaction.response.send_message( embed=discord.Embed( title="Unfollowed Twitch Channel", description=f"No longer following {twitch_name}.", - color=discord.Color.red() + color=discord.Color.red(), ) ) logger.info(f"No longer following {twitch_name}") @app_commands.command( - name="twitch_live", - description="Check if a Twitch streamer is live" + name="twitch_live", description="Check if a Twitch streamer is live" ) async def twitch_live(interaction: discord.Interaction, streamer: str): await interaction.response.defer() @@ -141,7 +153,7 @@ class TwitchModule: embed=discord.Embed( title="Twitch Live Check", description=f"Streamer {streamer} not found.", - color=discord.Color.red() + color=discord.Color.red(), ) ) logger.info(f"Streamer {streamer} not found.") @@ -158,10 +170,12 @@ class TwitchModule: f"**Game:** {streams.game_name}\n" f"**Viewers:** {streams.viewer_count}" ), - color=discord.Color.green() + color=discord.Color.green(), ) embed.set_thumbnail( - url=streams.thumbnail_url.replace('{width}', '320').replace('{height}', '180') + url=streams.thumbnail_url.replace("{width}", "320").replace( + "{height}", "180" + ) ) await interaction.followup.send(embed=embed) logger.info(f"Streamer {streamer} is live.") @@ -170,7 +184,7 @@ class TwitchModule: embed=discord.Embed( title=f"{streamer} is not live", description=f"{streamer} is currently offline.", - color=discord.Color.red() + color=discord.Color.red(), ) ) logger.info(f"Streamer {streamer} is offline.") diff --git a/modules/social/youtube_module.py b/modules/social/youtube_module.py index 99ccb1a..dffffbf 100644 --- a/modules/social/youtube_module.py +++ b/modules/social/youtube_module.py @@ -1,10 +1,12 @@ # modules/social/youtube_module.py +import asyncio +import logging + import discord from discord import app_commands from googleapiclient.discovery import build -import logging + import config -import asyncio from modules.data.db import get_connection logger = logging.getLogger(__name__) @@ -13,7 +15,7 @@ logger = logging.getLogger(__name__) class YouTubeModule: def __init__(self, bot): self.bot = bot - self.youtube = build('youtube', 'v3', developerKey=config.YOUTUBE_API_KEY) + self.youtube = build("youtube", "v3", developerKey=config.YOUTUBE_API_KEY) self.bot.loop.create_task(self.check_youtube_channels()) self.add_commands() @@ -21,34 +23,38 @@ class YouTubeModule: while True: conn = get_connection() c = conn.cursor() - c.execute('SELECT youtube_channel_id, discord_channel_id FROM followed_youtube_channels') + c.execute( + "SELECT youtube_channel_id, discord_channel_id FROM followed_youtube_channels" + ) followed_channels = c.fetchall() conn.close() for youtube_channel_id, discord_channel_id in followed_channels: try: request = self.youtube.channels().list( - part='contentDetails', - id=youtube_channel_id + part="contentDetails", id=youtube_channel_id ) response = request.execute() - if 'items' in response and len(response['items']) > 0: - uploads_playlist_id = response['items'][0]['contentDetails']['relatedPlaylists']['uploads'] - + if "items" in response and len(response["items"]) > 0: + uploads_playlist_id = response["items"][0]["contentDetails"][ + "relatedPlaylists" + ]["uploads"] + request = self.youtube.playlistItems().list( - part='snippet', - playlistId=uploads_playlist_id, - maxResults=1 + part="snippet", playlistId=uploads_playlist_id, maxResults=1 ) response = request.execute() - if 'items' in response and len(response['items']) > 0: - latest_video = response['items'][0]['snippet'] - video_id = latest_video['resourceId']['videoId'] - title = latest_video['title'] - publish_time = latest_video['publishedAt'] - thumbnail_url = latest_video['thumbnails']['high']['url'] + if "items" in response and len(response["items"]) > 0: + latest_video = response["items"][0]["snippet"] + video_id = latest_video["resourceId"]["videoId"] + title = latest_video["title"] + latest_video["publishedAt"] + thumbnail_url = latest_video["thumbnails"]["high"]["url"] - c.execute('SELECT last_video_id FROM youtube_status WHERE youtube_channel_id = ?', (youtube_channel_id,)) + c.execute( + "SELECT last_video_id FROM youtube_status WHERE youtube_channel_id = ?", + (youtube_channel_id,), + ) row = c.fetchone() last_video_id = row[0] if row else None @@ -58,15 +64,21 @@ class YouTubeModule: embed = discord.Embed( title=f"New Video from {youtube_channel_id}", description=f"{title}\n[Watch now](https://www.youtube.com/watch?v={video_id})", - color=discord.Color.green() + color=discord.Color.green(), ) embed.set_thumbnail(url=thumbnail_url) await channel.send(embed=embed) - c.execute('INSERT OR REPLACE INTO youtube_status (youtube_channel_id, last_video_id) VALUES (?, ?)', (youtube_channel_id, video_id)) + c.execute( + "INSERT OR REPLACE INTO youtube_status (youtube_channel_id, last_video_id) VALUES (?, ?)", + (youtube_channel_id, video_id), + ) conn.commit() except Exception as e: - logger.error(f"Error checking YouTube channel {youtube_channel_id}: {e}", exc_info=True) + logger.error( + f"Error checking YouTube channel {youtube_channel_id}: {e}", + exc_info=True, + ) conn.close() await asyncio.sleep(300) # Check every 5 minutes @@ -74,15 +86,19 @@ class YouTubeModule: def add_commands(self): @app_commands.command( name="follow_youtube", - description="Follow a YouTube channel to get video updates" + description="Follow a YouTube channel to get video updates", ) - async def follow_youtube(interaction: discord.Interaction, youtube_channel_id: str, channel: discord.TextChannel = None): + async def follow_youtube( + interaction: discord.Interaction, + youtube_channel_id: str, + channel: discord.TextChannel = None, + ): channel = channel or interaction.channel conn = get_connection() c = conn.cursor() c.execute( - 'INSERT OR REPLACE INTO followed_youtube_channels (youtube_channel_id, discord_channel_id) VALUES (?, ?)', - (youtube_channel_id, channel.id) + "INSERT OR REPLACE INTO followed_youtube_channels (youtube_channel_id, discord_channel_id) VALUES (?, ?)", + (youtube_channel_id, channel.id), ) conn.commit() conn.close() @@ -90,25 +106,28 @@ class YouTubeModule: embed=discord.Embed( title="Followed YouTube Channel", description=f"Now following {youtube_channel_id}. Alerts will be sent to {channel.mention}.", - color=discord.Color.green() + color=discord.Color.green(), ) ) - logger.info(f"Now following {youtube_channel_id} for video updates in {channel.name}") + logger.info( + f"Now following {youtube_channel_id} for video updates in {channel.name}" + ) @app_commands.command( - name="unfollow_youtube", - description="Unfollow a YouTube channel" + name="unfollow_youtube", description="Unfollow a YouTube channel" ) - async def unfollow_youtube(interaction: discord.Interaction, youtube_channel_id: str): + async def unfollow_youtube( + interaction: discord.Interaction, youtube_channel_id: str + ): conn = get_connection() c = conn.cursor() c.execute( - 'DELETE FROM followed_youtube_channels WHERE youtube_channel_id = ?', - (youtube_channel_id,) + "DELETE FROM followed_youtube_channels WHERE youtube_channel_id = ?", + (youtube_channel_id,), ) c.execute( - 'DELETE FROM youtube_status WHERE youtube_channel_id = ?', - (youtube_channel_id,) + "DELETE FROM youtube_status WHERE youtube_channel_id = ?", + (youtube_channel_id,), ) conn.commit() conn.close() @@ -116,7 +135,7 @@ class YouTubeModule: embed=discord.Embed( title="Unfollowed YouTube Channel", description=f"No longer following {youtube_channel_id}.", - color=discord.Color.red() + color=discord.Color.red(), ) ) logger.info(f"No longer following {youtube_channel_id}") diff --git a/modules/user/birthday_module.py b/modules/user/birthday_module.py index f1e1d14..0a2b582 100644 --- a/modules/user/birthday_module.py +++ b/modules/user/birthday_module.py @@ -1,9 +1,10 @@ # Flake8:noqa: E501 +import logging + import discord from discord import app_commands -import sqlite3 -import logging -from modules.data.db import initialize_db, get_connection + +from modules.data.db import get_connection, initialize_db logger = logging.getLogger(__name__) initialize_db() @@ -15,23 +16,23 @@ class BirthdayModule: self.add_commands() def add_commands(self): - @app_commands.command( - name="add_birthday", description="Add your birthday" - ) + @app_commands.command(name="add_birthday", description="Add your birthday") async def add_birthday(interaction: discord.Interaction, date: str): await interaction.response.defer() try: conn = get_connection() cursor = conn.cursor() - cursor.execute('INSERT OR REPLACE INTO birthdays (user_id, birthday) VALUES (?, ?)', - (str(interaction.user.id), date)) + cursor.execute( + "INSERT OR REPLACE INTO birthdays (user_id, birthday) VALUES (?, ?)", + (str(interaction.user.id), date), + ) conn.commit() conn.close() await interaction.followup.send( embed=discord.Embed( title="Add Birthday", description=f"Your birthday {date} has been added.", - color=discord.Color.green() + color=discord.Color.green(), ) ) logger.info(f"Birthday added for user {interaction.user.id}: {date}") @@ -39,15 +40,16 @@ class BirthdayModule: await interaction.followup.send(f"An error occurred: {e}") logger.error(f"Error in add_birthday command: {e}") - @app_commands.command( - name="view_birthday", description="View your birthday" - ) + @app_commands.command(name="view_birthday", description="View your birthday") async def view_birthday(interaction: discord.Interaction): await interaction.response.defer() try: conn = get_connection() cursor = conn.cursor() - cursor.execute('SELECT birthday FROM birthdays WHERE user_id = ?', (str(interaction.user.id),)) + cursor.execute( + "SELECT birthday FROM birthdays WHERE user_id = ?", + (str(interaction.user.id),), + ) result = cursor.fetchone() conn.close() if result: @@ -55,16 +57,18 @@ class BirthdayModule: embed=discord.Embed( title="View Birthday", description=f"Your birthday is {result[0]}.", - color=discord.Color.green() + color=discord.Color.green(), ) ) - logger.info(f"Birthday viewed for user {interaction.user.id}: {result[0]}") + logger.info( + f"Birthday viewed for user {interaction.user.id}: {result[0]}" + ) else: await interaction.followup.send( embed=discord.Embed( title="View Birthday", description="You have not set a birthday yet.", - color=discord.Color.red() + color=discord.Color.red(), ) ) logger.info(f"Birthday not found for user {interaction.user.id}") @@ -80,14 +84,17 @@ class BirthdayModule: try: conn = get_connection() cursor = conn.cursor() - cursor.execute('DELETE FROM birthdays WHERE user_id = ?', (str(interaction.user.id),)) + cursor.execute( + "DELETE FROM birthdays WHERE user_id = ?", + (str(interaction.user.id),), + ) conn.commit() conn.close() await interaction.followup.send( embed=discord.Embed( title="Remove Birthday", description="Your birthday has been removed.", - color=discord.Color.green() + color=discord.Color.green(), ) ) logger.info(f"Birthday removed for user {interaction.user.id}") diff --git a/modules/user/xp_module.py b/modules/user/xp_module.py index 780aa84..c47ade2 100644 --- a/modules/user/xp_module.py +++ b/modules/user/xp_module.py @@ -1,8 +1,11 @@ +import logging +import random +from datetime import datetime, timedelta + import discord from discord import app_commands -import logging + from modules.data.db import get_connection -from datetime import datetime, timedelta logger = logging.getLogger(__name__) @@ -12,25 +15,30 @@ class XPModule: self.bot = bot self.cooldown = {} # Dictionary to store cooldowns self.add_commands() + self.setup_event_listeners() async def on_message(self, message): + logger.debug(f"Received message from {message.author.id}") if message.author.bot: + logger.debug("Message is from a bot, ignoring") return user_id = message.author.id now = datetime.now() - if user_id in self.cooldown: - if now < self.cooldown[user_id]: - return # User is on cooldown + if user_id in self.cooldown and now < self.cooldown[user_id]: + logger.debug(f"User {user_id} is on cooldown, ignoring") + return # User is on cooldown - self.give_xp(user_id, 10) # Give 10 XP for a message - self.cooldown[user_id] = now + timedelta(seconds=60) # 1 minute cooldown + xp = random.randint(1, 5) # Award between 1 and 5 XP for a message + logger.debug(f"Awarding {xp} XP to user {user_id}") + self.give_xp(user_id, xp) + self.cooldown[user_id] = now + timedelta(seconds=60) # 1 minute cooldown # noqa: E501 def give_xp(self, user_id, xp): conn = get_connection() c = conn.cursor() - c.execute('SELECT xp, level FROM user_xp WHERE user_id = ?', (user_id,)) + c.execute("SELECT xp, level FROM user_xp WHERE user_id = ?", (user_id,)) # noqa: E501 row = c.fetchone() if row: @@ -39,45 +47,55 @@ class XPModule: new_level = current_level # Level up logic - if new_xp >= self.xp_for_next_level(current_level): + while new_xp >= self.xp_for_next_level(new_level): + new_xp -= self.xp_for_next_level(new_level) new_level += 1 - new_xp = new_xp - self.xp_for_next_level(current_level) logger.info(f"User {user_id} leveled up to {new_level}") - c.execute('UPDATE user_xp SET xp = ?, level = ? WHERE user_id = ?', (new_xp, new_level, user_id)) + c.execute( + "UPDATE user_xp SET xp = ?, level = ? WHERE user_id = ?", + (new_xp, new_level, user_id), + ) else: - c.execute('INSERT INTO user_xp (user_id, xp, level) VALUES (?, ?, ?)', (user_id, xp, 1)) + c.execute( + "INSERT INTO user_xp (user_id, xp, level) VALUES (?, ?, ?)", + (user_id, xp, 1), + ) conn.commit() conn.close() + logger.debug(f"Updated XP for user {user_id}") def xp_for_next_level(self, level): - return 100 * level # Example leveling curve + return int( + 100 * (1.5 ** (level - 1)) + ) # Exponential scaling for XP required to level up def add_commands(self): - @app_commands.command(name='xp', description='Check your XP and level') + @app_commands.command(name="xp", description="Check your XP and level") async def check_xp(interaction: discord.Interaction): user_id = interaction.user.id conn = get_connection() c = conn.cursor() - c.execute('SELECT xp, level FROM user_xp WHERE user_id = ?', (user_id,)) + c.execute("SELECT xp, level FROM user_xp WHERE user_id = ?", (user_id,)) # noqa: E501 row = c.fetchone() conn.close() if row: xp, level = row - await interaction.response.send_message(f"You have {xp} XP and are level {level}.") + await interaction.response.send_message( + f"You have {xp} XP and are level {level}." + ) else: await interaction.response.send_message("You have no XP yet.") self.bot.tree.add_command(check_xp) + def setup_event_listeners(self): + @self.bot.event + async def on_message(message): + await self.on_message(message) + async def setup(bot): - xp_module = XPModule(bot) - - @bot.event - async def on_message(message): - if not message.author.bot: # Ensure the bot doesn't earn XP - await xp_module.on_message(message) - await bot.process_commands(message) # Process commands after the XP check \ No newline at end of file + XPModule(bot)