diff --git a/.gitignore b/.gitignore index cfbe543..886492e 100644 --- a/.gitignore +++ b/.gitignore @@ -160,4 +160,6 @@ cython_debug/ #.idea/ data/ -.vscode/ \ No newline at end of file +.vscode/ + +*.disabled \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3259dc2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM python:3.13.14-alpine + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +ENV TOKEN="" +ENV URL="" + + +ENTRYPOINT ["python", "main.py"] \ No newline at end of file diff --git a/cogs/cs.py b/cogs/cs.py new file mode 100644 index 0000000..7cfb1df --- /dev/null +++ b/cogs/cs.py @@ -0,0 +1,375 @@ +import discord, datetime, os, re, json +from selenium import webdriver +from bs4 import BeautifulSoup +from discord.ext import commands, tasks, bridge +from setup.logger import LOGGER +from models.cs_models import * + +BASE_URL = "https://www.hltv.org" + +PAGE_FILE = os.path.join("data","hltv.html") + +MATCHES_FILE = os.path.join("data","matches.json") + +# Cache duration in seconds (6 hours) +DATA_CACHE_DURATION = 3600 * 6 + +# Match age threshold in seconds (2 days) +OLD_MATCH_DURATION = 3600 * 24 * 2 + +# Reaction emojis for match events +EMOJI_CREATE_EVENT = "✅" # Create guild event for the match +EMOJI_REMOVE_EVENT = "❌" # Remove guild event for the match + +class CSCog(commands.Cog): + def __init__(self, bot:discord.Bot): + self.bot = bot + self.matches:list[Match] = [] + self.message_match_map = {} # Maps message IDs to Match objects + self.get_matches.start() + + def is_up_to_date(self) -> bool: + """Check if PAGE_FILE and MATCHES_FILE are up to date.""" + try: + page_modified = datetime.datetime.fromtimestamp(os.stat(PAGE_FILE).st_ctime) + matches_modified = datetime.datetime.fromtimestamp(os.stat(MATCHES_FILE).st_ctime) + + now = datetime.datetime.now() + page_age = now.timestamp() - page_modified.timestamp() + matches_age = now.timestamp() - matches_modified.timestamp() + + return page_age <= DATA_CACHE_DURATION and matches_age <= DATA_CACHE_DURATION + except FileNotFoundError: + return False + + def get_page(self) -> None: + """Get the page from https://www.hltv.org/matches/ using Firefox webdriver and save to PAGE_FILE.""" + driver = webdriver.Firefox() + driver.get("https://www.hltv.org/matches/") + html = driver.page_source + driver.close() + + with open(PAGE_FILE, 'w') as f: + f.write(html) + + def save(self) -> None: + """Save self.matches to MATCHES_FILE.""" + matches_data = [match.to_dict() for match in self.matches] + + with open(MATCHES_FILE, 'w') as f: + json.dump(matches_data, f, indent=2) + + def remove_old_matches(self) -> None: + """Remove matches that are older than OLD_MATCH_DURATION.""" + now = datetime.datetime.now() + filtered_matches = [] + + for match in self.matches: + if match.date is None: + # Keep matches with no date + filtered_matches.append(match) + else: + match_age = now.timestamp() - match.date.timestamp() + if match_age <= OLD_MATCH_DURATION: + filtered_matches.append(match) + + self.matches = filtered_matches + + @tasks.loop(minutes=5) + async def get_matches(self): + if self.is_up_to_date(): + # Load from MATCHES_FILE + try: + with open(MATCHES_FILE) as f: + matches_data = json.load(f) + + self.matches = [Match.from_dict(match_data) for match_data in matches_data] + self.remove_old_matches() + except Exception as e: + LOGGER.error(f"Error loading matches from cache: {e}") + self.matches = [] + else: + # Fetch new data + self.get_page() + + # Read and parse the page + with open(PAGE_FILE) as f: + html = f.read() + + page = BeautifulSoup(html) + + all_matches = [] + for section in page.find_all("div", {"class": "matches-chronologically"}): + all_matches.extend(section.find_all("div", {"class": "match-wrapper"})) + + self.matches = [] + for match_event in all_matches: + event = match_event.find("div", {"class": "match-event"}) + event_name = event.attrs["data-event-headline"] + + # Extract event logo + event_logo_container = event.find("div", {"class": "match-event-logo-container"}) + event_logo_url = None + if event_logo_container: + logo_img = event_logo_container.find("img") + if logo_img and logo_img.attrs.get("src"): + event_logo_url = logo_img.attrs["src"] + + event_obj = Event(int(event.attrs["data-event-id"]), event_name, event_logo_url) + + href = str(match_event.find("a", {"class": "match-top"}).attrs["href"]) + match_href = "https://hltv.org" + href + + match_time_elt = match_event.find("div", {"class": "match-time"}) + if match_time_elt: + match_timestamp = int(match_time_elt.attrs["data-unix"]) + match_date = datetime.datetime.fromtimestamp(match_timestamp/1000) + else: + match_date = None + + match_format = match_event.find("div", {"class": "match-meta"}).text + + teams = match_event.find_all("div", {"class": "match-team"}) + match_teams = [] + for match_team in teams: + team = match_team.find("div", {"class": "match-teamname"}) + team_obj = Team(team.text, match_team.find("img").attrs["src"]) + match_teams.append(team_obj) + + if len(match_teams) == 0: + continue + + match_obj = Match(match_href, match_format, event_obj, match_teams, match_date) + self.matches.append(match_obj) + + # Remove old matches and save to cache + self.remove_old_matches() + self.save() + + if self.matches: + print(str(self.matches[0])) + + def check_event(self, match: Match, event: discord.ScheduledEvent) -> bool: + """ + Check if a scheduled event matches the given match. + Returns True if both team names appear in the event name (case-insensitive). + """ + event_name_lower = event.name.lower() + team1_name_lower = match.teams[0].name.lower() + team2_name_lower = match.teams[1].name.lower() + + # Check if both team names are in the event name + return team1_name_lower in event_name_lower and team2_name_lower in event_name_lower + + async def create_guild_event(self, guild: discord.Guild, match: Match) -> discord.ScheduledEvent | None: + """ + Create a scheduled guild event for the match. + Returns the created event or None if it already exists or creation fails. + """ + try: + # Check if event already exists + for event in guild.scheduled_events: + if self.check_event(match, event): + return event + + # Create event name + event_name = f"{match.teams[0].name} vs {match.teams[1].name}" + + # Set start and end times - use UTC timezone to match the embed display + if match.date: + # Convert the timestamp to UTC-aware datetime + timestamp = int(match.date.timestamp()) + start_time = datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc) + end_time = start_time + datetime.timedelta(hours=2) # Assume 2 hour match + else: + start_time = datetime.datetime.now(tz=datetime.timezone.utc) + end_time = start_time + datetime.timedelta(hours=2) + + # Create the event + event = await guild.create_scheduled_event( + name=event_name, + start_time=start_time, + end_time=end_time, + description=f"Event: {match.event.name}\nFormat: {match.format}\nWatch on HLTV: {match.url}", + location="" + ) + LOGGER.info(f"Created guild event for {event_name} in {guild.name}") + return event + except Exception as e: + LOGGER.error(f"Error creating guild event in {guild.name}: {e}") + return None + + async def remove_guild_event(self, guild: discord.Guild, match: Match) -> bool: + """ + Remove the scheduled guild event for the match. + Returns True if an event was removed, False otherwise. + """ + try: + for event in guild.scheduled_events: + if self.check_event(match, event): + await event.delete() + LOGGER.info(f"Removed guild event {event.name} from {guild.name}") + return True + return False + except Exception as e: + LOGGER.error(f"Error removing guild event from {guild.name}: {e}") + return False + + def create_match_embed(self, match: Match) -> discord.Embed: + """ + Create a Discord embed message containing match information. + + Args: + match: The Match object to display + + Returns: + discord.Embed: A formatted embed with match details + """ + # Create embed with title as the matchup + title = f"{match.teams[0].name} vs {match.teams[1].name}" + embed = discord.Embed( + title=title, + description=f"**Event:** {match.event.name}", + url=match.url, + color=discord.Color.blue() + ) + + # Add match format + embed.add_field( + name="Format", + value=match.format, + inline=True + ) + + # Add match date if available + if match.date: + embed.add_field( + name="Date & Time", + value=f"", + inline=True + ) + + # Add team information with logos + teams_info = f"[{match.teams[0].name}]({match.url})\n[{match.teams[1].name}]({match.url})" + embed.add_field( + name="Teams", + value=teams_info, + inline=False + ) + + # Set thumbnail to event logo if available, otherwise use first team logo + if match.event.logo_url: + embed.set_thumbnail(url=match.event.logo_url) + elif match.teams[0].logo_url: + embed.set_thumbnail(url=match.teams[0].logo_url) + + # Set footer with HLTV link + embed.set_footer( + text="HLTV.org", + icon_url="https://www.hltv.org/img/static/TopLogo2x.png" + ) + + return embed + + @bridge.bridge_command() + async def test_match_embed(self, ctx:bridge.BridgeExtContext): + """Send the first match as an embed to the test channel.""" + TEST_CHANNEL_ID = 1305147782071451718 + + # Check if there are matches available + if not self.matches: + await ctx.send("❌ No matches available. Try running the scraper first.") + return + + # Get the first match + first_match = self.matches[0] + + # Create the embed + embed = self.create_match_embed(first_match) + + # Send to test channel + try: + channel = self.bot.get_channel(TEST_CHANNEL_ID) + if channel is None: + await ctx.send(f"❌ Could not find channel with ID {TEST_CHANNEL_ID}") + return + + message = await channel.send(embed=embed) + + # Add reactions + await message.add_reaction(EMOJI_CREATE_EVENT) + await message.add_reaction(EMOJI_REMOVE_EVENT) + + # Track the message -> match mapping + self.message_match_map[message.id] = first_match + + await ctx.reply(f"✅ Sent embed for match: {first_match.teams[0].name} vs {first_match.teams[1].name}") + + except Exception as e: + LOGGER.error(f"Error sending embed: {e}") + await ctx.send(f"❌ Error sending embed: {e}") + + @commands.Cog.listener() + async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent): + """Handle reactions added to match embed messages.""" + # Ignore reactions from the bot itself + if payload.user_id == self.bot.user.id: + return + + # Check if this is a tracked message + if payload.message_id not in self.message_match_map: + return + + match = self.message_match_map[payload.message_id] + emoji = payload.emoji.name + + # Get the guild + guild = self.bot.get_guild(payload.guild_id) + if guild is None: + return + + # Get the message for replies + message = await self.get_reaction_message(payload) + if not message: + return + + try: + if emoji == EMOJI_CREATE_EVENT: + # Create event + event = await self.create_guild_event(guild, match) + if event: + await message.reply(f"✅ Created guild event: **{event.name}**") + elif emoji == EMOJI_REMOVE_EVENT: + # Remove event + removed = await self.remove_guild_event(guild, match) + if removed: + await message.reply( + f"✅ Removed guild event for {match.teams[0].name} vs {match.teams[1].name}" + ) + else: + await message.reply( + f"⚠️ No guild event found for this match" + ) + except Exception as e: + LOGGER.error(f"Error handling reaction {emoji}: {e}") + + async def get_reaction_message(self, payload: discord.RawReactionActionEvent) -> discord.Message | None: + """Get the message object from a raw reaction payload.""" + try: + channel = self.bot.get_channel(payload.channel_id) + if channel: + return await channel.fetch_message(payload.message_id) + except Exception as e: + LOGGER.error(f"Error fetching message: {e}") + return None + async def check_events(self): + """Check scheduled events for matching CS matches.""" + for match in self.matches: + for guild in self.bot.guilds: + events = [event for event in guild.scheduled_events if self.check_event(match, event)] + # print(guild.name, match) + + # @commands.Cog.listener() + # async def on_ready(self): + # self.check_events.start() + diff --git a/cogs/insult.py b/cogs/insult.py new file mode 100644 index 0000000..57a21c5 --- /dev/null +++ b/cogs/insult.py @@ -0,0 +1,31 @@ +import discord, json, os, random, re +import collections +from discord.ext import commands +from setup.logger import LOGGER + +class InsultsCog(commands.Cog): + def __init__(self, bot:discord.Bot): + self.bot = bot + + with open(os.path.join("data", "insults.txt")) as f: + self.insults = [l.strip() for l in f.readlines()] + + @commands.Cog.listener() + async def on_message(self, message:discord.Message): + if len(message.content.split()) > 3: + author = message.author + if any(author.id == u.id for u in self.men): + r = int(random.random()*100) + print(r) + if r < 5: + insult = random.choice(self.insults) + await message.reply(insult) + + @commands.Cog.listener() + async def on_ready(self): + self.men:list[discord.Member] = [] + for guild in self.bot.guilds: + for role in guild.roles: + if re.match(r'^([hH]im|[hH]e).*$',role.name): + self.men.extend(role.members) + self.men = list(set(self.men)) \ No newline at end of file diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..be44ad3 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,9 @@ +services: + botmafieux: + build: + context: . + dockerfile: ./Dockerfile + tags: + - nebulo9/botmafieux:v2 + container_name: botmafieux + env_file: ./.env \ No newline at end of file diff --git a/main.py b/main.py index ea1f498..a783859 100644 --- a/main.py +++ b/main.py @@ -1,164 +1,129 @@ import os, discord, json, sys, random -import modules.setup.db as db from argparse import ArgumentParser from datetime import datetime from asyncio import create_task from dotenv import load_dotenv from discord import option from discord.ext import tasks, commands -from modules.setup.bot import bot, reload_feature -from modules.setup.logger import LOGGER -from modules.cogs.productivity import TASKS as PRODUCTIVITY_TASKS, send_reminder - +from setup.bot import bot, reload_feature +from setup.logger import LOGGER PARSER = ArgumentParser(description='BotMafieux for Discord.') PARSER.add_argument('--guild_id',type=int,help='The guild ID.') PARSER.add_argument('--channel_id',type=int,help='The channel ID.') PARSER.add_argument('-r','--reload',action='store_true',help='Reload the bot.') EXEC_ARGS = PARSER.parse_args() -@tasks.loop(time=datetime.time(datetime.strptime('00:00','%H:%M'))) -async def birthday_anouncements_task(): - LOGGER.debug('birthday_anouncements_task started.') - for guild in bot.guilds: - birthday_settings = db.select('birthday_settings',f'guild_id = {guild.id}') - if birthday_settings: - if birthday_settings['is_enabled']: - for user in guild.members: - user_productivity_data = db.select('guild_user_productivity',f'user_id = {user.id} AND guild_id = {guild.id}') - if user_productivity_data: - if user_productivity_data['is_enabled']: - user = guild.get_member(user.id) - if user: - birthday = user_productivity_data['birthday'] - today = datetime.today().strftime('%d/%m') - if birthday == today: - channel = guild.get_channel(user_productivity_data['channel_id']) - if channel: - message = user_productivity_data['birthday_message'] - if message: - message = message.replace('{user}',user.mention) - await channel.send(message) - else: - LOGGER.debug(f'Channel {user_productivity_data["channel_id"]} not found.') - else: - LOGGER.debug(f'No birthday today for user {user.name} ({user.id}).') - else: - LOGGER.debug(f'User {user.name} ({user.id}) not found.') - else: - LOGGER.debug(f'User {user.name} ({user.id}) has birthday announcements disabled.') - else: - LOGGER.debug(f'No birthday data found for user {user.name} ({user.id}).') - else: - LOGGER.debug(f'No productivity settings found for guild {guild.name} ({guild.id}).') - LOGGER.debug('birthday_anouncements_task ended.') +# @tasks.loop(time=datetime.time(datetime.strptime('00:00','%H:%M'))) +# async def birthday_anouncements_task(): +# LOGGER.debug('birthday_anouncements_task started.') +# for guild in bot.guilds: +# birthday_settings = db.select('birthday_settings',f'guild_id = {guild.id}') +# if birthday_settings: +# if birthday_settings['is_enabled']: +# for user in guild.members: +# user_productivity_data = db.select('guild_user_productivity',f'user_id = {user.id} AND guild_id = {guild.id}') +# if user_productivity_data: +# if user_productivity_data['is_enabled']: +# user = guild.get_member(user.id) +# if user: +# birthday = user_productivity_data['birthday'] +# today = datetime.today().strftime('%d/%m') +# if birthday == today: +# channel = guild.get_channel(user_productivity_data['channel_id']) +# if channel: +# message = user_productivity_data['birthday_message'] +# if message: +# message = message.replace('{user}',user.mention) +# await channel.send(message) +# else: +# LOGGER.debug(f'Channel {user_productivity_data["channel_id"]} not found.') +# else: +# LOGGER.debug(f'No birthday today for user {user.name} ({user.id}).') +# else: +# LOGGER.debug(f'User {user.name} ({user.id}) not found.') +# else: +# LOGGER.debug(f'User {user.name} ({user.id}) has birthday announcements disabled.') +# else: +# LOGGER.debug(f'No birthday data found for user {user.name} ({user.id}).') +# else: +# LOGGER.debug(f'No productivity settings found for guild {guild.name} ({guild.id}).') +# LOGGER.debug('birthday_anouncements_task ended.') -@bot.slash_command(name='help',description='Displays the help message.') -async def help(ctx:discord.ApplicationContext): - """Displays this message.""" - command_name = 'help' - LOGGER.debug(f'{ctx.author.name} used /{command_name}.') - embed = discord.Embed(title='Help',description='List of commands and their descriptions.',color=discord.Color.from_rgb(171,0,219)) - embed.set_author(name=bot.user.name,icon_url=bot.user.avatar.url) - for command in bot.commands: - embed.add_field(name=command.name,value=command.description,inline=False) - await ctx.send_response(embed=embed,ephemeral=True) +# @bot.slash_command(name='help',description='Displays the help message.') +# async def help(ctx:discord.ApplicationContext): +# """Displays this message.""" +# command_name = 'help' +# LOGGER.debug(f'{ctx.author.name} used /{command_name}.') +# embed = discord.Embed(title='Help',description='List of commands and their descriptions.',color=discord.Color.from_rgb(171,0,219)) +# embed.set_author(name=bot.user.name,icon_url=bot.user.avatar.url) +# for command in bot.commands: +# embed.add_field(name=command.name,value=command.description,inline=False) +# await ctx.send_response(embed=embed,ephemeral=True) -@bot.slash_command(name='reload',description='Reloads the bot.') -@option(name='feature',description='The feature to reload.',required=True,choices=['birthday','productivity']) -@commands.is_owner() -async def reload(ctx:discord.ApplicationContext, feature:str): - """Reloads the bot.""" - command_name = 'reload' - guild = ctx.guild - channel = ctx.channel - LOGGER.debug(f'{ctx.author.name} used /{command_name} {feature}.') - try: - reload_feature(feature) - await ctx.send_response(f'{feature} reloaded!',ephemeral=True) - except Exception as e: - LOGGER.error(f'Error reloading feature {feature}: {e}') - await ctx.send_response(f'Error reloading feature {feature}: {e}',ephemeral=True) +# @bot.slash_command(name='reload',description='Reloads the bot.') +# @option(name='feature',description='The feature to reload.',required=True,choices=['birthday','productivity']) +# @commands.is_owner() +# async def reload(ctx:discord.ApplicationContext, feature:str): +# """Reloads the bot.""" +# command_name = 'reload' +# guild = ctx.guild +# channel = ctx.channel +# LOGGER.debug(f'{ctx.author.name} used /{command_name} {feature}.') +# try: +# reload_feature(feature) +# await ctx.send_response(f'{feature} reloaded!',ephemeral=True) +# except Exception as e: +# LOGGER.error(f'Error reloading feature {feature}: {e}') +# await ctx.send_response(f'Error reloading feature {feature}: {e}',ephemeral=True) -@bot.event -async def on_member_join(member:discord.Member): - if not member.bot: - user_id = member.id - user_name = member.name - user_mention = member.mention - db_user = db.select('global_user',f'WHERE user_id = {user_id}') - if not db_user: - db.insert('global_user',user_id=user_id,user_name=user_name,user_mention=user_mention) +# @bot.event +# async def on_member_join(member:discord.Member): +# if not member.bot: +# user_id = member.id +# user_name = member.name +# user_mention = member.mention +# db_user = db.select('global_user',f'WHERE user_id = {user_id}') +# if not db_user: +# db.insert('global_user',user_id=user_id,user_name=user_name,user_mention=user_mention) -@bot.event -async def on_raw_member_remove(payload:discord.RawMemberRemoveEvent): - user = payload.user - if not user.bot: - user_id = user.id - guild_id = payload.guild_id - db.delete('guild_user_birthday',f'user_id = {user_id} AND guild_id = {guild_id}') - db.delete('guild_user_productivity',f'user_id = {user_id} AND guild_id = {guild_id}') +# @bot.event +# async def on_raw_member_remove(payload:discord.RawMemberRemoveEvent): +# user = payload.user +# if not user.bot: +# user_id = user.id +# guild_id = payload.guild_id +# db.delete('guild_user_birthday',f'user_id = {user_id} AND guild_id = {guild_id}') +# db.delete('guild_user_productivity',f'user_id = {user_id} AND guild_id = {guild_id}') -@bot.event -async def on_guild_join(guild:discord.Guild): - guild_id = guild.id - guild_name = guild.name - LOGGER.info(f'Bot joined guild {guild_name} ({guild_id}).') - db.insert('guild',guild_id=guild_id) - db.insert('birthday_settings',guild_id=guild_id,is_enabled=False,channel_id=random.choice(guild.text_channels).id) - db.insert('productivity_settings',guild_id=guild_id,is_enabled=False) +# @bot.event +# async def on_guild_join(guild:discord.Guild): +# guild_id = guild.id +# guild_name = guild.name +# LOGGER.info(f'Bot joined guild {guild_name} ({guild_id}).') +# db.insert('guild',guild_id=guild_id) +# db.insert('birthday_settings',guild_id=guild_id,is_enabled=False,channel_id=random.choice(guild.text_channels).id) +# db.insert('productivity_settings',guild_id=guild_id,is_enabled=False) -@bot.event -async def on_guild_remove(guild:discord.Guild): - guild_id = guild.id - guild_name = guild.name - LOGGER.info(f'Bot left guild {guild_name} ({guild_id}).') - db.delete('birthday_settings',f'guild_id = {guild_id}') - db.delete('productivity_settings',f'guild_id = {guild_id}') - db.delete('guild',f'guild_id = {guild_id}') +# @bot.event +# async def on_guild_remove(guild:discord.Guild): +# guild_id = guild.id +# guild_name = guild.name +# LOGGER.info(f'Bot left guild {guild_name} ({guild_id}).') +# db.delete('birthday_settings',f'guild_id = {guild_id}') +# db.delete('productivity_settings',f'guild_id = {guild_id}') +# db.delete('guild',f'guild_id = {guild_id}') @bot.event async def on_ready(): - LOGGER.info(f'{bot.user.name} has connected to Discord!') - LOGGER.debug(f'Guilds: {bot.guilds}') - if EXEC_ARGS.reload and EXEC_ARGS.guild_id and EXEC_ARGS.channel_id: - guild = bot.get_guild(EXEC_ARGS.guild_id) - channel = guild.get_channel_or_thread(EXEC_ARGS.channel_id) - await channel.send(f'{bot.user.name} Bot reloaded!',silent=True) - birthday_anouncements_task.start() - for guild in bot.guilds: - for user in guild.members: - user_id = user.id - db_user = db.select('global_user',f'user_id = {user_id}') - if not db_user: - db.insert('global_user',user_id=user_id,user_name=user.name,user_mention=user.mention) - - productivity_settings = db.select('productivity_settings',f'guild_id = {guild.id}') - if productivity_settings: - if productivity_settings['is_enabled']: - for user in guild.members: - user_productivity_data = db.select('guild_user_productivity',f'user_id = {user.id} AND guild_id = {guild.id}') - if user_productivity_data: - if user_productivity_data['is_enabled']: - next_reminder = user_productivity_data['next_reminder'] - if next_reminder > datetime.now().timestamp(): - cooldown = next_reminder - datetime.now().timestamp() - channel = guild.get_channel(user_productivity_data['channel_id']) - reminder_message = user_productivity_data['reminder_message'] - PRODUCTIVITY_TASKS[str(user.id)] = create_task(send_reminder(cooldown,user,channel,reminder_message)) - else: - user_productivity_data['next_reminder'] = datetime.now().timestamp() + user_productivity_data['cooldown'] - db.update('guild_user_productivity',f'user_id = {user.id} AND guild_id = {guild.id}',next_reminder=user_productivity_data['next_reminder']) - channel = guild.get_channel(user_productivity_data['channel_id']) - reminder_message = user_productivity_data['reminder_message'] - PRODUCTIVITY_TASKS[str(user.id)] = create_task(send_reminder(user_productivity_data['cooldown'],user,channel,reminder_message)) - else: - LOGGER.debug(f'No productivity settings found for guild {guild.name} ({guild.id}).') + if bot.user: + LOGGER.info(f'{bot.user.name} has connected to Discord!') + LOGGER.debug(f'Guilds: {','.join([guild.name for guild in bot.guilds])}') + if __name__ == '__main__': - db.check_conn() - result = db.select('token','token_name = \'discord\'','token_value') - if result: - TOKEN = result['token_value'] + load_dotenv() + TOKEN = os.getenv("TOKEN") + if TOKEN: bot.run(TOKEN) else: LOGGER.error('No token found.') diff --git a/models/cs_models.py b/models/cs_models.py new file mode 100644 index 0000000..6351088 --- /dev/null +++ b/models/cs_models.py @@ -0,0 +1,119 @@ +import datetime + +class Event: + def __init__(self, event_id:int, name:str, logo_url:str = None) -> None: + self.__event_id = event_id + self.__name = name + self.__logo_url = logo_url + + @property + def event_id(self): + return self.__event_id + + @property + def name(self): + return self.__name + + @property + def logo_url(self): + return self.__logo_url + + def to_dict(self) -> dict: + return { + 'event_id': self.event_id, + 'name': self.name, + 'logo_url': self.logo_url + } + + @classmethod + def from_dict(cls, data: dict) -> 'Event': + return cls(data['event_id'], data['name'], data.get('logo_url')) + + def __str__(self) -> str: + return f"" + + +class Team: + def __init__(self, name:str, logo_url:str) -> None: + self.__name = name + self.__logo_url = logo_url + + @property + def name(self): + return self.__name + + @property + def logo_url(self): + return self.__logo_url + + def to_dict(self) -> dict: + return { + 'name': self.name, + 'logo_url': self.logo_url + } + + @classmethod + def from_dict(cls, data: dict) -> 'Team': + return cls(data['name'], data['logo_url']) + + def __str__(self) -> str: + return f"" + +class Match: + def __init__(self, url:str, format:str, event:Event, teams:list[Team], date=None): + self.__date = date + self.__url = url + self.__format = format + self.__event = event + self.__teams = tuple(teams) + + @property + def date(self): + return self.__date + + @property + def url(self): + return self.__url + + @property + def format(self): + return self.__format + + @property + def event(self): + return self.__event + + @property + def teams(self): + return self.__teams + + @property + def date_iso(self): + return self.__date.isoformat() + + @property + def teams_names(self): + return (self.__teams[0].name, self.__teams[1].name) + + @property + def teams_logos(self): + return (self.__teams[0].logo_url, self.__teams[1].logo_url) + + def to_dict(self) -> dict: + return { + 'url': self.url, + 'format': self.format, + 'event': self.event.to_dict(), + 'teams': [team.to_dict() for team in self.teams], + 'date': self.date.isoformat() if self.date else None + } + + @classmethod + def from_dict(cls, data: dict) -> 'Match': + event = Event.from_dict(data['event']) + teams = [Team.from_dict(team_data) for team_data in data['teams']] + date = datetime.datetime.fromisoformat(data['date']) if data['date'] else None + return cls(data['url'], data['format'], event, teams, date) + + def __str__(self) -> str: + return f"" \ No newline at end of file diff --git a/modules/cogs/birthday.py b/modules/cogs/birthday.py deleted file mode 100644 index d2dc515..0000000 --- a/modules/cogs/birthday.py +++ /dev/null @@ -1,116 +0,0 @@ -import discord, re -from discord import option -from discord.ext import commands -from datetime import datetime -from ..setup.logger import LOGGER -from ..setup import db - -class BirthdayCog(commands.Cog): - def __init__(self, bot): - self.bot = bot - - @commands.slash_command(description='Sets birthday date. Must be in DAY/MONTH format.') - @option(name='date',description='Birthday date.',required=True) - @option(name='announcements',description='Enable or disable birthday announcements.',required=True,type=bool) - @option(name='message',description='The message to send when it is the user\'s birthday.',required=False) - @option(name='for_user',description='The user to set the birthday date for.',required=False,type=discord.Member) - async def birthday_set(self,ctx:discord.ApplicationContext, date:str,announcements:bool,message:str,for_user:discord.Member): - """Sets birthday date. Must be in DAY/MONTH format.""" - command_name = 'birthday_set' - guild_id = ctx.guild.id - author = ctx.author - birthday_settings = db.select('birthday_settings',f'guild_id = {guild_id}') - if birthday_settings: - if birthday_settings['is_enabled']: - if for_user: - if author.guild_permissions.administrator: - if re.match(r'^\d{1,2}\/\d{1,2}$', date): - user_data = db.select('guild_user_birthday',f'guild_id = {guild_id} AND user_id = {for_user.id}') - if user_data: - # Update user data - if message: - LOGGER.debug(f'{author.name} used /{command_name} {date} {announcements} {message} for {for_user.name}.') - db.update('guild_user_birthday',f'guild_id = {guild_id} AND user_id = {for_user.id}',birthday=date,is_enabled=announcements,birthday_message=message) - else: - LOGGER.debug(f'{author.name} used /{command_name} {date} {announcements} for {for_user.name}.') - db.update('guild_user_birthday',f'guild_id = {guild_id} AND user_id = {for_user.id}',birthday=date,is_enabled=announcements) - await ctx.send_response(f'{for_user.name}\'s birthday has been set to {date}.',ephemeral=True) - else: - # Create user data - if message: - LOGGER.debug(f'{author.name} used /{command_name} {date} {announcements} {message} for {for_user.name}.') - db.insert('guild_user_birthday',guild_id=guild_id,user_id=for_user.id,birthday=date,is_enabled=announcements,birthday_message=message) - else: - LOGGER.debug(f'{author.name} used /{command_name} {date} {announcements} for {for_user.name}.') - db.insert('guild_user_birthday',guild_id=guild_id,user_id=for_user.id,birthday=date,is_enabled=announcements) - await ctx.send_response(f'{for_user.name}\'s birthday has been set to {date}.',ephemeral=True) - else: - LOGGER.debug(f'{author.name} tried to use /{command_name} {date} {announcements} {message} for {for_user.name} but the date format is invalid.') - await ctx.send_response('The date format must be DAY/MONTH.',ephemeral=True) - else: - LOGGER.debug(f'{author.name} tried to use /{command_name} {date} {announcements} {message} for {for_user.name} but is not an administrator.') - await ctx.send_response('You must be an administrator to run the command with "for_user".',ephemeral=True) - else: - if re.match(r'^\d{1,2}\/\d{1,2}$', date): - user_data = db.select('guild_user_birthday',f'guild_id = {guild_id} AND user_id = {author.id}') - if user_data: - # Update user data - if message: - LOGGER.debug(f'{author.name} used /{command_name} {date} {announcements} {message}.') - db.update('guild_user_birthday',f'guild_id = {guild_id} AND user_id = {author.id}',birthday=date,is_enabled=announcements,birthday_message=message) - else: - LOGGER.debug(f'{author.name} used /{command_name} {date} {announcements}.') - db.update('guild_user_birthday',f'guild_id = {guild_id} AND user_id = {author.id}',birthday=date,is_enabled=announcements) - await ctx.send_response(f'Your birthday has been set to {date}.',ephemeral=True) - else: - # Create user data - if message: - LOGGER.debug(f'{author.name} used /{command_name} {date} {announcements} {message}.') - db.insert('guild_user_birthday',guild_id=guild_id,user_id=author.id,birthday=date,is_enabled=announcements,birthday_message=message) - else: - LOGGER.debug(f'{author.name} used /{command_name} {date} {announcements}.') - db.insert('guild_user_birthday',guild_id=guild_id,user_id=author.id,birthday=date,is_enabled=announcements) - await ctx.send_response(f'Your birthday has been set to {date}.',ephemeral=True) - else: - LOGGER.debug(f'{author.name} tried to use /{command_name} {date} {announcements} {message} but the date format is invalid.') - await ctx.send_response('The date format must be DAY/MONTH.',ephemeral=True) - else: - LOGGER.debug(f'{author.name} tried to use /{command_name} {date} {announcements} {message} but the birthday feature is not enabled.') - await ctx.send_response('The birthday feature is not enabled.',ephemeral=True) - else: - LOGGER.debug(f'{author.name} tried to use /{command_name} {date} {announcements} {message} but the birthday feature is not enabled.') - await ctx.send_response('The birthday feature is not enabled.',ephemeral=True) - - @commands.slash_command(description='Configurate birthday announcements.') - @option(name='channel',description='The channel to send birthday announcements to.',required=True,type=discord.TextChannel) - @option(name='enable',description='Enable or disable birthday announcements.',required=True,type=bool) - async def birthday_config(self,ctx:discord.ApplicationContext, channel:discord.TextChannel,enable:bool): - """Configurate birthday announcements.""" - command_name = 'birthday_config' - guild_id = ctx.guild.id - author = ctx.author - birthday_settings = db.select('birthday_settings',f'guild_id = {guild_id}') - if birthday_settings: - # Update guild birthday settings - if birthday_settings['is_enabled']: - if author.guild_permissions.administrator: - LOGGER.debug(f'{author.name} used /{command_name} {channel.name} {enable}.') - db.update('birthday_settings',f'guild_id = {guild_id}',channel_id=channel.id,is_enabled=enable) - await ctx.send_response(f'Birthday announcements have been {"enabled" if enable else "disabled"} in {channel.mention}.',ephemeral=True) - else: - LOGGER.debug(f'{author.name} tried to use /{command_name} {channel.name} {enable} but is not an administrator.') - await ctx.send_response('You must be an administrator to run this command.',ephemeral=True) - else: - LOGGER.debug(f'{author.name} tried to use /{command_name} {channel.name} {enable} but the birthday feature is not enabled.') - await ctx.send_response('The birthday feature is not enabled.',ephemeral=True) - else: - # Create guild birthday settings - if author.guild_permissions.administrator: - LOGGER.debug(f'{author.name} used /{command_name} {channel.name} {enable}.') - db.insert('birthday_settings',guild_id=guild_id,channel_id=channel.id,is_enabled=enable) - await ctx.send_response(f'Birthday announcements have been {"enabled" if enable else "disabled"} in {channel.mention}.',ephemeral=True) - else: - LOGGER.debug(f'{author.name} tried to use /{command_name} {channel.name} {enable} but is not an administrator.') - await ctx.send_response('You must be an administrator to run this command.',ephemeral=True) - - diff --git a/modules/cogs/productivity.py b/modules/cogs/productivity.py deleted file mode 100644 index b21e360..0000000 --- a/modules/cogs/productivity.py +++ /dev/null @@ -1,150 +0,0 @@ -import discord -from datetime import datetime -from discord.ext import commands, tasks -from discord import option -from typing import Union, Optional -from asyncio import sleep, create_task -from ..setup.logger import LOGGER -from ..setup import db - -TASKS = {} - -async def send_reminder(cooldown:int,author:discord.Member,channel:discord.TextChannel,reminder_message:str): - while True: - await sleep(cooldown) - LOGGER.debug(f'Sending productivity reminder to {author.name} in {channel.name} of {channel.guild.name}.') - message = reminder_message.replace('{user}',author.mention) - await channel.send(message) - -class ProductivityCog(commands.Cog): - - def __init__(self,bot:discord.Bot) -> None: - self.bot = bot - - @commands.slash_command(description='Configurate productivity reminder.') - @option(name='channel',description='The channel to send productivity reminders in.',required=True) - @option(name='enable',description='Enable or disable productivity reminders.',required=True) - @option(name='days',description='Number of days to wait before sending a reminder if the user has not been active in the channel.',required=True,type=int) - @option(name='custom_message',description='Custom message to send with the reminder.',required=False,type=str) - @option(name='for_user',description='The user to configurate productivity reminders for.',required=False,type=discord.Member) - async def productivity_set(self,ctx:discord.ApplicationContext, channel:Union[discord.TextChannel,discord.Thread], enable:bool, days:int, custom_message=None, for_user:discord.Member=None): - """Configurate productivity reminder.""" - command_name = 'productivity_set' - guild_id = ctx.guild.id - author = ctx.author - productivity_settings = db.select('productivity_settings',f'guild_id = {guild_id}') - if productivity_settings: - if productivity_settings['is_enabled']: - if for_user: - if author.guild_permissions.administrator: - user_data = db.select('guild_user_productivity',f'guild_id = {guild_id} AND user_id = {for_user.id}') - cooldown = days*60*60*24 - next_reminder = int(datetime.now().timestamp() + cooldown) - if user_data: - # Update user data - if custom_message: - db.update('guild_user_productivity',f'guild_id = {guild_id} AND user_id = {for_user.id}',channel_id=channel.id,cooldown=cooldown,next_reminder=next_reminder,reminder_message=custom_message,is_enabled=enable) - LOGGER.debug(f'{author.name} used /{command_name} {channel.mention} {enable} {days} {custom_message} for {for_user.name}.') - await ctx.send_response(f'Productivity reminders for {for_user.name} have been set to {channel.mention}.',ephemeral=True) - else: - db.update('guild_user_productivity',f'guild_id = {guild_id} AND user_id = {for_user.id}',channel_id=channel.id,cooldown=cooldown,next_reminder=next_reminder,is_enabled=enable) - LOGGER.debug(f'{author.name} used /{command_name} {channel.mention} {enable} {days} for {for_user.name}.') - await ctx.send_response(f'Productivity reminders for {for_user.name} have been set to {channel.mention}.',ephemeral=True) - else: - # Create user data - if custom_message: - db.insert('guild_user_productivity',guild_id=guild_id,user_id=for_user.id,channel_id=channel.id,cooldown=cooldown,next_reminder=next_reminder,reminder_message=custom_message,is_enabled=enable) - LOGGER.debug(f'{author.name} used /{command_name} {channel.mention} {enable} {days} {custom_message} for {for_user.name}.') - await ctx.send_response(f'Productivity reminders for {for_user.name} have been set to {channel.mention}.',ephemeral=True) - else: - db.insert('guild_user_productivity',guild_id=guild_id,user_id=for_user.id,channel_id=channel.id,cooldown=cooldown,next_reminder=next_reminder,is_enabled=enable) - LOGGER.debug(f'{author.name} used /{command_name} {channel.mention} {enable} {days} for {for_user.name}.') - await ctx.send_response(f'Productivity reminders for {for_user.name} have been set to {channel.mention}.',ephemeral=True) - user_data = db.select('guild_user_productivity',f'guild_id = {guild_id} AND user_id = {for_user.id}') - if str(for_user.id) in TASKS.keys(): - TASKS[str(for_user.id)].cancel() - TASKS[str(for_user.id)] = create_task(send_reminder(cooldown,for_user,channel,user_data['reminder_message'])) - else: - LOGGER.debug(f'{author.name} tried to use /{command_name} {channel.mention} {enable} {days} {custom_message} for {for_user.name} but is not an administrator.') - await ctx.send_response('You must be an administrator to run the command with "for_user".',ephemeral=True) - else: - user_data = db.select('guild_user_productivity',f'guild_id = {guild_id} AND user_id = {author.id}') - cooldown = days*60*60*24 - next_reminder = int(datetime.now().timestamp() + cooldown) - if user_data: - # Update user data - if custom_message: - db.update('guild_user_productivity',f'guild_id = {guild_id} AND user_id = {author.id}',channel_id=channel.id,cooldown=cooldown,next_reminder=next_reminder,reminder_message=custom_message,is_enabled=enable) - LOGGER.debug(f'{author.name} used /{command_name} {channel.mention} {enable} {days} {custom_message}.') - await ctx.send_response(f'Productivity reminders for {author.name} have been set to {channel.mention}.',ephemeral=True) - else: - db.update('guild_user_productivity',f'guild_id = {guild_id} AND user_id = {author.id}',channel_id=channel.id,cooldown=cooldown,next_reminder=next_reminder,is_enabled=enable) - LOGGER.debug(f'{author.name} used /{command_name} {channel.mention} {enable} {days}.') - await ctx.send_response(f'Productivity reminders for {author.name} have been set to {channel.mention}.',ephemeral=True) - else: - # Create user data - if custom_message: - db.insert('guild_user_productivity',guild_id=guild_id,user_id=author.id,channel_id=channel.id,cooldown=cooldown,next_reminder=next_reminder,reminder_message=custom_message,is_enabled=enable) - LOGGER.debug(f'{author.name} used /{command_name} {channel.mention} {enable} {days} {custom_message}.') - await ctx.send_response(f'Productivity reminders for {author.name} have been set to {channel.mention}.',ephemeral=True) - else: - db.insert('guild_user_productivity',guild_id=guild_id,user_id=author.id,channel_id=channel.id,cooldown=cooldown,next_reminder=next_reminder,is_enabled=enable) - LOGGER.debug(f'{author.name} used /{command_name} {channel.mention} {enable} {days}.') - await ctx.send_response(f'Productivity reminders for {author.name} have been set to {channel.mention}.',ephemeral=True) - user_data = db.select('guild_user_productivity',f'guild_id = {guild_id} AND user_id = {author.id}') - if str(author.id) in TASKS.keys(): - TASKS[str(author.id)].cancel() - TASKS[str(author.id)] = create_task(send_reminder(cooldown,author,channel,user_data['reminder_message'])) - else: - LOGGER.debug(f'{author.name} tried to use /{command_name} {channel.mention} {enable} {days} {custom_message} but the productivity feature is not enabled.') - await ctx.send_response('The productivity feature is not enabled.',ephemeral=True) - else: - LOGGER.debug(f'{author.name} tried to use /{command_name} {channel.mention} {enable} {days} {custom_message} but no productivity settings were found.') - await ctx.send_response('No productivity settings were found.',ephemeral=True) - - @commands.slash_command(description='Enable or disable productivity reminders on the server.') - @option(name='enable',description='Enable or disable productivity reminders.',required=True) - async def productivity_config(self,ctx:discord.ApplicationContext, enable:bool): - """Enable or disable productivity reminders on the server.""" - command_name = 'productivity_config' - guild_id = ctx.guild.id - author = ctx.author - productivity_settings = db.select('productivity_settings',f'guild_id = {guild_id}') - if productivity_settings: - # Update guild productivity settings - if author.guild_permissions.administrator: - LOGGER.debug(f'{author.name} used /{command_name} {enable}.') - db.update('productivity_settings',f'guild_id = {guild_id}',is_enabled=enable) - await ctx.send_response(f'Productivity reminders have been {"enabled" if enable else "disabled"}.',ephemeral=True) - else: - LOGGER.debug(f'{author.name} tried to use /{command_name} {enable} but is not an administrator.') - await ctx.send_response('You must be an administrator to run this command.',ephemeral=True) - else: - # Create guild productivity settings - if author.guild_permissions.administrator: - LOGGER.debug(f'{author.name} used /{command_name} {enable}.') - db.insert('productivity_settings',guild_id=guild_id,is_enabled=enable) - await ctx.send_response(f'Productivity reminders have been {"enabled" if enable else "disabled"}.',ephemeral=True) - else: - LOGGER.debug(f'{author.name} tried to use /{command_name} {enable} but is not an administrator.') - await ctx.send_response('You must be an administrator to run this command.',ephemeral=True) - - @commands.Cog.listener() - async def on_message(self,message:discord.Message): - author = message.author - if author.id != self.bot.user.id: # Ignore messages from the bot - guild_id = message.guild.id - productivity_settings = db.select('productivity_settings',f'guild_id = {guild_id}') - if productivity_settings: - if productivity_settings['is_enabled']: - user_productivity_data = db.select('guild_user_productivity',f'user_id = {author.id} AND guild_id = {guild_id}') - if user_productivity_data: - if user_productivity_data['is_enabled']: - next_reminder = int(datetime.now().timestamp() + user_productivity_data['cooldown']) - db.update('guild_user_productivity',f'user_id = {author.id} AND guild_id = {guild_id}',next_reminder=next_reminder) - if str(author.id) in TASKS.keys(): - TASKS[str(author.id)].cancel() - TASKS[str(author.id)] = create_task(send_reminder(user_productivity_data['cooldown'],author,message.channel,user_productivity_data['reminder_message'])) - - - \ No newline at end of file diff --git a/modules/setup/db.py b/modules/setup/db.py deleted file mode 100644 index 842a17b..0000000 --- a/modules/setup/db.py +++ /dev/null @@ -1,304 +0,0 @@ -import os, psycopg2 -from dotenv import load_dotenv -from typing import Optional, Callable, Any, Union -from .logger import LOGGER - -load_dotenv() -DB_NAME = os.getenv('DB_NAME') -DB_USER = os.getenv('DB_USER') -DB_PASSWORD = os.getenv('DB_PASSWORD') -DB_HOST = os.getenv('DB_HOST') - -def check_conn(): - """Tests the database connection.""" - connection = psycopg2.connect( - database=DB_NAME, - user=DB_USER, - password=DB_PASSWORD, - host=DB_HOST, - port="5432" - ) - if connection.status: - LOGGER.debug('Established connection to database.') - LOGGER.debug(f'Connection status: {connection.status}') - else: - LOGGER.error('Failed to connect to database.') - connection.close() - LOGGER.debug('Closed connection to database.') - -def insert(table:str,**kwargs): - """ - Inserts the given data into the given table. - - Global_User - user_id: int - user_mention: str - user_mention: str - - Guild - guild_id: int - - Birthday_Settings - guild_id: int - is_enabled: bool - channel_id: int - - Productivity_Settings - guild_id: int - is_enabled: bool - - Guild_User_Birthday - user_id: int - guild_id: int - birthday: int - birthday_message: str | None - is_enabled: bool - - Guild_User_Productivity - user_id: int - guild_id: int - is_enabled: bool - channel_id: int - cooldown: int - next_reminder: int - reminder_message: str | None - """ - connection = psycopg2.connect( - database=DB_NAME, - user=DB_USER, - password=DB_PASSWORD, - host=DB_HOST, - port="5432", - ) - cursor = connection.cursor() - - query = f"INSERT INTO {table} (" - params = [] - for key,value in kwargs.items(): - query += f"{key}," - params.append(value) - query = query.rstrip(",") # Remove trailing comma - query += ") VALUES (" - for _ in range(len(params)): - query += "%s," - query = query.rstrip(",") # Remove trailing comma - query += ")" - - query = cursor.mogrify(query, params) - cursor.execute(query) - connection.commit() - cursor.close() - connection.close() - LOGGER.debug(f'Inserted data into {table}.') - -def update(table:str,where:str,**kwargs): - """ - Updates the given data in the given table. - - Global_User - user_id: int - user_mention: str - user_mention: str - - Guild - guild_id: int - - Birthday_Settings - guild_id: int - is_enabled: bool - channel_id: int - - Productivity_Settings - guild_id: int - is_enabled: bool - - Guild_User_Birthday - user_id: int - guild_id: int - birthday: int - birthday_message: str | None - is_enabled: bool - - Guild_User_Productivity - user_id: int - guild_id: int - is_enabled: bool - channel_id: int - cooldown: int - next_reminder: int - reminder_message: str | None - """ - connection = psycopg2.connect( - database=DB_NAME, - user=DB_USER, - password=DB_PASSWORD, - host=DB_HOST, - port="5432", - ) - cursor = connection.cursor() - - query = f"UPDATE {table} SET " - params = [] - for key,value in kwargs.items(): - query += f"{key} = %s," - params.append(value) - query = query.rstrip(",") # Remove trailing comma - query += f" WHERE {where}" - - query = cursor.mogrify(query, params) - cursor.execute(query) - connection.commit() - cursor.close() - connection.close() - LOGGER.debug(f'Updated data in {table}.') - -def delete(table:str,where:str): - """ - Deletes the given data from the given table. - - Global_User - user_id: int - user_mention: str - user_mention: str - - Guild - guild_id: int - - Birthday_Settings - guild_id: int - is_enabled: bool - channel_id: int - - Productivity_Settings - guild_id: int - is_enabled: bool - - Guild_User_Birthday - user_id: int - guild_id: int - birthday: int - birthday_message: str | None - is_enabled: bool - - Guild_User_Productivity - user_id: int - guild_id: int - is_enabled: bool - channel_id: int - cooldown: int - next_reminder: int - reminder_message: str | None - """ - connection = psycopg2.connect( - database=DB_NAME, - user=DB_USER, - password=DB_PASSWORD, - host=DB_HOST, - port="5432", - ) - cursor = connection.cursor() - - query = f"DELETE FROM {table} WHERE {where}" - cursor.execute(query) - connection.commit() - cursor.close() - connection.close() - LOGGER.debug(f'Deleted data from {table}.') - -def select(table:str,where:str,*columns:str) -> dict[str,Any] | None: - """ - Selects the given data from the given table, and returns it as a dictionary. - - Global_User - user_id: int - user_mention: str - user_mention: str - - Guild - guild_id: int - - Birthday_Settings - guild_id: int - is_enabled: bool - channel_id: int - - Productivity_Settings - guild_id: int - is_enabled: bool - - Guild_User_Birthday - user_id: int - guild_id: int - birthday: int - birthday_message: str | None - is_enabled: bool - - Guild_User_Productivity - user_id: int - guild_id: int - is_enabled: bool - channel_id: int - cooldown: int - next_reminder: int - reminder_message: str | None - """ - connection = psycopg2.connect( - database=DB_NAME, - user=DB_USER, - password=DB_PASSWORD, - host=DB_HOST, - port="5432" - ) - connection.readonly = True - cursor = connection.cursor() - - query = f"SELECT " - if columns: - for column in columns: - query += f"{column}," - query = query.rstrip(",") - else: - query += "*" - query += f" FROM {table} WHERE {where}" - cursor.execute(query) - record = cursor.fetchone() - result = None - if record is None: - LOGGER.debug(f'No data found in {table} for {where}.') - else: - result = {} - if len(columns) > 0: - for i in range(len(columns)): - result[columns[i]] = record[i] - else: - if table.lower() == 'global_user': - result['user_id'] = record[0] - result['user_name'] = record[1] - result['user_mention'] = record[2] - elif table.lower() == 'guild': - result['guild_id'] = record[0] - elif table.lower() == 'birthday_settings': - result['guild_id'] = record[0] - result['is_enabled'] = record[1] - result['channel_id'] = record[2] - elif table.lower() == 'productivity_settings': - result['guild_id'] = record[0] - result['is_enabled'] = record[1] - elif table.lower() == 'guild_user_birthday': - result['user_id'] = record[0] - result['guild_id'] = record[1] - result['birthday'] = record[2] - result['is_enabled'] = record[3] - elif table.lower() == 'guild_user_productivity': - result['user_id'] = record[0] - result['guild_id'] = record[1] - result['is_enabled'] = record[2] - result['channel_id'] = record[3] - result['cooldown'] = record[4] - result['next_reminder'] = record[5] - result['reminder_message'] = record[6] - else: - LOGGER.error(f'Unknown table {table}.') - cursor.close() - connection.close() - return result \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index b09a0fc..956c7f9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,31 @@ -python-dotenv -py-cord -psycopg2-binary \ No newline at end of file +aiohappyeyeballs==2.6.2 +aiohttp==3.14.1 +aiosignal==1.4.0 +asarPy==1.0.1 +attrs==26.1.0 +beautifulsoup4==4.15.0 +certifi==2026.5.20 +charset-normalizer==3.4.7 +frozenlist==1.8.0 +h11==0.16.0 +idna==3.18 +lxml==6.1.1 +multidict==6.7.1 +outcome==1.3.0.post0 +propcache==0.5.2 +psycopg2-binary==2.9.12 +py-cord==2.8.0 +PySocks==1.7.1 +python-dotenv==1.2.2 +requests==2.34.2 +selenium==4.45.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +soupsieve==2.8.4 +trio==0.33.0 +trio-websocket==0.12.2 +typing_extensions==4.15.0 +urllib3==2.7.0 +websocket-client==1.9.0 +wsproto==1.3.2 +yarl==1.24.2 diff --git a/modules/setup/bot.py b/setup/bot.py similarity index 63% rename from modules/setup/bot.py rename to setup/bot.py index 3a9c9d2..116204d 100644 --- a/modules/setup/bot.py +++ b/setup/bot.py @@ -1,7 +1,10 @@ import discord -from ..cogs.birthday import BirthdayCog -from ..cogs.productivity import ProductivityCog -from .logger import LOGGER +from discord.ext import bridge +# from cogs.birthday import BirthdayCog +# from cogs.productivity import ProductivityCog +from cogs.cs import CSCog +from cogs.insult import InsultsCog +from setup.logger import LOGGER bot_intents = discord.Intents.default() bot_intents.message_content = True @@ -10,9 +13,9 @@ bot_intents.presences = True bot_intents.guilds = True bot_intents.reactions = True -bot = discord.Bot(command_prefix='$', intents=bot_intents) +bot = bridge.Bot(command_prefix='$', intents=bot_intents) -COGS = [BirthdayCog, ProductivityCog] +COGS = [CSCog, InsultsCog] for cog in COGS: bot.add_cog(cog(bot)) diff --git a/modules/setup/logger.py b/setup/logger.py similarity index 100% rename from modules/setup/logger.py rename to setup/logger.py