diff --git a/core/database.py b/core/database.py index 84c7188..62fb79c 100644 --- a/core/database.py +++ b/core/database.py @@ -312,6 +312,12 @@ class FinalizedEmailVerification(BaseModel): email = TextField() class_year = TextField() +class StarboardMessage(BaseModel): + id = AutoField() + original_message_id = IntegerField(unique=True) + starboard_message_id = IntegerField() + star_count = IntegerField() + # Function to initialize the database def initialize_db(): db.connect() @@ -366,7 +372,8 @@ def _db_close(exc): "ColorUser": ColorUser, "ClassSchedule": ClassSchedule, "EmailVerification": EmailVerification, - "FinalizedEmailVerification": FinalizedEmailVerification + "FinalizedEmailVerification": FinalizedEmailVerification, + "StarboardMessage": StarboardMessage } """ diff --git a/core/rolecolors.py b/core/rolecolors.py index 234a33b..5b19726 100644 --- a/core/rolecolors.py +++ b/core/rolecolors.py @@ -4,7 +4,7 @@ Classes: RoleColorModal: A modal for users to input a hex code to change their role color. RoleNameModal: A modal for users to input a new name for their role. - CustomizeView: A persistent view with buttons to trigger the role customization modals and manage a specific role. + CustomizeView: A persistent view with a dropdown to trigger the role customization modals and manage a specific role. Only avaliable in the Guild: r(evolution)pi """ @@ -12,7 +12,7 @@ import traceback import discord -from discord.ui import View, Modal, TextInput +from discord.ui import View, Modal, TextInput, Select, SelectOption from core import database @@ -28,6 +28,7 @@ async def on_submit(self, interaction: discord.Interaction): return user_record = database.ColorUser.select().where(database.ColorUser.user_id == interaction.user.id) + reference_role = interaction.guild.get_role(1216596310619328642) if user_record.exists(): role = interaction.guild.get_role(user_record.get().role_id) @@ -36,18 +37,18 @@ async def on_submit(self, interaction: discord.Interaction): await interaction.response.send_message(f"Role {role.name} edited.", ephemeral=True) else: role_name = interaction.user.name + f"'s Role ({hex_code})" - role = await interaction.guild.create_role(name=role_name, colour=discord.Colour(int(hex_code[1:], 16)), reason=f"{interaction.user.name} requested color change to {hex_code}") + role = await interaction.guild.create_role(name=role_name, colour=discord.Colour(int(hex_code[1:], 16)), reason=f"{interaction.user.name} requested color change to {hex_code}", position=reference_role.position + 1) database.update_user_role(interaction.user.id, role.id) await interaction.response.send_message(f"Role {role.name} assigned.", ephemeral=True) else: role_name = interaction.user.name + f"'s Role ({hex_code})" - role = await interaction.guild.create_role(name=role_name, colour=discord.Colour(int(hex_code[1:], 16)), reason=f"{interaction.user.name} requested color change to {hex_code}") + role = await interaction.guild.create_role(name=role_name, colour=discord.Colour(int(hex_code[1:], 16)), reason=f"{interaction.user.name} requested color change to {hex_code}", position=reference_role.position + 1) query = database.ColorUser.create(user_id=interaction.user.id, role_id=role.id) query.save() database.update_user_role(interaction.user.id, role.id) + await interaction.user.add_roles(role, reason=f"{interaction.user.name} requested color change to {hex_code}") await interaction.response.send_message(f"Role {role.name} given!", ephemeral=True) - await interaction.user.add_roles(role, reason=f"{interaction.user.name} requested color change to {hex_code}") async def on_error(self, interaction: discord.Interaction, error: Exception) -> None: await interaction.response.send_message('Oops! Something went wrong. Contact rohit if this keeps happening :(.', ephemeral=True) @@ -63,6 +64,7 @@ async def on_submit(self, interaction: discord.Interaction): role_name = self.role_name.value user_record = database.ColorUser.select().where(database.ColorUser.user_id == interaction.user.id) + reference_role = interaction.guild.get_role(1216596310619328642) if user_record.exists(): role = interaction.guild.get_role(user_record.get().role_id) @@ -72,20 +74,19 @@ async def on_submit(self, interaction: discord.Interaction): else: random_hex_code = discord.Color.random() role_name = interaction.user.name + f"'s Role ({random_hex_code.value})" - role = await interaction.guild.create_role(name=role_name, colour=random_hex_code, reason=f"{interaction.user.name} requested name change to {role_name}") + role = await interaction.guild.create_role(name=role_name, colour=random_hex_code, reason=f"{interaction.user.name} requested name change to {role_name}", position=reference_role.position + 1) database.update_user_role(interaction.user.id, role.id) await interaction.response.send_message(f"Role {role.name} assigned.", ephemeral=True) else: random_hex_code = discord.Color.random() role_name = interaction.user.name + f"'s Role ({random_hex_code.value})" - role = await interaction.guild.create_role(name=role_name, colour=random_hex_code, reason=f"{interaction.user.name} requested name change to {role_name}") - + role = await interaction.guild.create_role(name=role_name, colour=random_hex_code, reason=f"{interaction.user.name} requested name change to {role_name}", position=reference_role.position + 1) query = database.ColorUser.create(user_id=interaction.user.id, role_id=role.id) query.save() database.update_user_role(interaction.user.id, role.id) + await interaction.user.add_roles(role, reason=f"{interaction.user.name} requested name change to {role_name}") await interaction.response.send_message(f"Role {role.name} given!", ephemeral=True) - await interaction.user.add_roles(role, reason=f"{interaction.user.name} requested name change to {role_name}") async def on_error(self, interaction: discord.Interaction, error: Exception) -> None: await interaction.response.send_message('Oops! Something went wrong. Contact rohit if this keeps happening :(.', ephemeral=True) @@ -97,23 +98,38 @@ async def on_error(self, interaction: discord.Interaction, error: Exception) -> class CustomizeView(View): def __init__(self): super().__init__(timeout=None) # Persistent view - - @discord.ui.button(label="Customize Role Color", style=discord.ButtonStyle.primary, custom_id="customize_role_color") - async def customize_button_color(self, interaction: discord.Interaction, button: discord.ui.Button): - modal = RoleColorModal(title="Customize Your Role Color") - await interaction.response.send_modal(modal) - - @discord.ui.button(label="Customize Role Name", style=discord.ButtonStyle.gray, custom_id="customize_role_name") - async def customize_button_name(self, interaction: discord.Interaction, button: discord.ui.Button): - modal = RoleNameModal(title="Customize Your Role Name") - await interaction.response.send_modal(modal) - - @discord.ui.button(label="Get Roblox Role", style=discord.ButtonStyle.danger, custom_id="get_roblox_role") - async def get_roblox_role(self, interaction: discord.Interaction, button: discord.ui.Button): - roblox_role = discord.utils.get(interaction.guild.roles, id=1239657271143698564) - if roblox_role in interaction.user.roles: - await interaction.user.remove_roles(roblox_role, reason="User requested to remove Roblox role") - await interaction.response.send_message("Roblox role removed!", ephemeral=True) - else: - await interaction.user.add_roles(roblox_role, reason="User requested to add Roblox role") - await interaction.response.send_message("Roblox role added!", ephemeral=True) + options = [ + SelectOption(label="Customize Role Color", value="customize_role_color", emoji="🎨"), + SelectOption(label="Customize Role Name", value="customize_role_name", emoji="📝"), + SelectOption(label="Get Roblox Role", value="get_roblox_role", emoji="🤖"), + SelectOption(label="Get Valorant Role", value="get_valorant_role", emoji="🔫") + ] + self.add_item(CustomizeDropdown(options=options)) + +class CustomizeDropdown(Select): + def __init__(self, options): + super().__init__(placeholder="Choose an action...", min_values=1, max_values=1, options=options) + + async def callback(self, interaction: discord.Interaction): + if self.values[0] == "customize_role_color": + modal = RoleColorModal(title="Customize Your Role Color") + await interaction.response.send_modal(modal) + elif self.values[0] == "customize_role_name": + modal = RoleNameModal(title="Customize Your Role Name") + await interaction.response.send_modal(modal) + elif self.values[0] == "get_roblox_role": + roblox_role = discord.utils.get(interaction.guild.roles, id=1239657271143698564) + if roblox_role in interaction.user.roles: + await interaction.user.remove_roles(roblox_role, reason="User requested to remove Roblox role") + await interaction.response.send_message("Roblox role removed!", ephemeral=True) + else: + await interaction.user.add_roles(roblox_role, reason="User requested to add Roblox role") + await interaction.response.send_message("Roblox role added!", ephemeral=True) + elif self.values[0] == "get_valorant_role": + valorant_role = discord.utils.get(interaction.guild.roles, id=1249529840034512946) + if valorant_role in interaction.user.roles: + await interaction.user.remove_roles(valorant_role, reason="User requested to remove Valorant role") + await interaction.response.send_message("Valorant role removed!", ephemeral=True) + else: + await interaction.user.add_roles(valorant_role, reason="User requested to add Valorant role") + await interaction.response.send_message("Valorant role added!", ephemeral=True) \ No newline at end of file diff --git a/core/rpi/email_verification.py b/core/rpi/email_verification.py index d540c7f..d2cd69a 100644 --- a/core/rpi/email_verification.py +++ b/core/rpi/email_verification.py @@ -24,7 +24,10 @@ def get_gmail_service(): creds = pickle.load(token) if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: - creds.refresh(Request()) + try: + creds.refresh(Request()) + except: + raise Exception("Error refreshing token") # Sentry needs to pick this up. else: flow = InstalledAppFlow.from_client_secrets_file( 'credentials.json', SCOPES) @@ -94,7 +97,11 @@ async def on_submit(self, interaction: discord.Interaction): q = database.EmailVerification.create(discord_id=interaction.user.id, email=rpi_email, verification_code=verification_code, class_year=self.class_year.value) q.save() - service = get_gmail_service() + try: + service = get_gmail_service() + except: + await interaction.response.send_message("Looks like the Gmail API is down. Please try again later. (Contact <@409152798609899530> if this keeps happening.)", ephemeral=True) + return message = create_message(rpi_email, f' RPIcord Verification Code: {verification_code}', f'Use the code {verification_code} to verify your email address for the RPI Class of 2028 Discord Server.\n\nIf you did not request this, please ignore this email.') send_message(service, 'me', message) await interaction.response.send_message(f'Verification email sent to {rpi_email}. Please check your *(junk)* inbox and use the code to verify.\n\n> **Use /verification verify_code to finalize verification!**', ephemeral=True) diff --git a/core/rpi/regen_gmail_token.py b/core/rpi/regen_gmail_token.py new file mode 100644 index 0000000..22ccfa0 --- /dev/null +++ b/core/rpi/regen_gmail_token.py @@ -0,0 +1,31 @@ +import os +import pickle +from google.auth.transport.requests import Request +from google_auth_oauthlib.flow import InstalledAppFlow +from googleapiclient.discovery import build + +# If modifying these SCOPES, delete the file token.pickle. +SCOPES = ['https://www.googleapis.com/auth/gmail.send'] + +def get_gmail_service(): + creds = None + if os.path.exists('token.pickle'): + with open('token.pickle', 'rb') as token: + creds = pickle.load(token) + # If there are no (valid) credentials available, let the user log in. + if not creds or not creds.valid: # Still gotta fix the not creds.valid check. + if creds and creds.expired and creds.refresh_token: + creds.refresh(Request()) + else: + flow = InstalledAppFlow.from_client_secrets_file( + 'credentials.json', SCOPES) + creds = flow.run_local_server(port=0) + + with open('token.pickle', 'wb') as token: + pickle.dump(creds, token) + service = build('gmail', 'v1', credentials=creds) + return service + +if __name__ == '__main__': + service = get_gmail_service() + print("Token.pickle file generated successfully.") \ No newline at end of file diff --git a/core/rpi/starboard.py b/core/rpi/starboard.py new file mode 100644 index 0000000..fc569c9 --- /dev/null +++ b/core/rpi/starboard.py @@ -0,0 +1,73 @@ +import discord +from discord.ext import commands +from core.database import StarboardMessage + +class Starboard(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.starboard_channel_id = 123456789012345678 # Default starboard channel ID + self.star_threshold = 5 # Default number of stars required to post to starboard + + @commands.Cog.listener() + async def on_raw_reaction_add(self, payload): + if payload.emoji.name == '⭐': # Check if the reaction is a star + await self.handle_star_reaction(payload) + + @commands.Cog.listener() + async def on_raw_reaction_remove(self, payload): + if payload.emoji.name == '⭐': # Check if the reaction is a star + await self.handle_star_reaction(payload) + + async def handle_star_reaction(self, payload): + channel = self.bot.get_channel(payload.channel_id) + message = await channel.fetch_message(payload.message_id) + star_count = sum(1 for reaction in message.reactions if reaction.emoji == '⭐') + + starboard_channel = self.bot.get_channel(self.starboard_channel_id) + starboard_message = StarboardMessage.get_or_none(StarboardMessage.original_message_id == message.id) + + if star_count >= self.star_threshold: + embed = discord.Embed( + description=message.content, + color=discord.Color.gold() + ) + embed.set_author(name=message.author.display_name, icon_url=message.author.avatar.url) + embed.add_field(name="Jump to message", value=f"[Click Here]({message.jump_url})") + embed.set_footer(text=f"⭐ {star_count} | {message.channel.name}") + + if starboard_message: + starboard_msg = await starboard_channel.fetch_message(starboard_message.starboard_message_id) + await starboard_msg.edit(embed=embed) + starboard_message.star_count = star_count + starboard_message.save() + else: + starboard_msg = await starboard_channel.send(embed=embed) + StarboardMessage.create( + original_message_id=message.id, + starboard_message_id=starboard_msg.id, + star_count=star_count + ) + elif starboard_message: + starboard_msg = await starboard_channel.fetch_message(starboard_message.starboard_message_id) + await starboard_msg.delete_instance() + + @commands.group(name="starboard", invoke_without_command=True) + async def starboard(self, ctx): + await ctx.send("Available subcommands: setchannel, setthreshold") + + @starboard.command(name="setchannel") + @commands.has_permissions(administrator=True) + async def set_channel(self, ctx, channel: discord.TextChannel): + """Set the starboard channel.""" + self.starboard_channel_id = channel.id + await ctx.send(f"Starboard channel set to {channel.mention}") + + @starboard.command(name="setthreshold") + @commands.has_permissions(administrator=True) + async def set_threshold(self, ctx, threshold: int): + """Set the star count threshold.""" + self.star_threshold = threshold + await ctx.send(f"Star count threshold set to {threshold}") + +async def setup(bot): + await bot.add_cog(Starboard(bot)) \ No newline at end of file diff --git a/main.py b/main.py index 96f3c42..1b8a264 100644 --- a/main.py +++ b/main.py @@ -128,6 +128,12 @@ async def on_message(self, message: discord.Message): await self.process_commands(message) + async def on_member_join(self, member: discord.Member): + if member.guild.id == 1216429016760717322 and not member.bot: + welcome_channel = await self.bot.fetch_channel(1216429018744885321) + + await welcome_channel.send(f"hi loser, {member.mention}. im just here to tell you that if you want a custom role, go to <#1219037652414894310>. bye. also keira sucks lol") + async def is_owner(self, user: discord.User): """ Checks if the user is the owner of the bot. @@ -201,4 +207,4 @@ def start_time(self): ) if __name__ == "__main__": - bot.run(os.getenv("TOKEN")) + bot.run(os.getenv("TOKEN")) \ No newline at end of file diff --git a/utils/rpicord28/quacs_util.py b/utils/rpicord28/quacs_util.py index f295fb7..995a12f 100644 --- a/utils/rpicord28/quacs_util.py +++ b/utils/rpicord28/quacs_util.py @@ -67,6 +67,38 @@ class RegistrationCog(commands.Cog): def __init__(self, bot): self.bot = bot self.course_data = CourseData() + self.ap_credit_mapping = { + "Art and Design 2-D": {4: "ARTS-2220", 5: "ARTS-2220"}, + "Art and Design 3-D": {4: "ARTS-2210", 5: "ARTS-2210"}, + "Drawing": {4: "ARTS-1200", 5: "ARTS-1200"}, + "Art History": {4: "ARTS-1050", 5: "ARTS-1050"}, + "Chinese Language and Culture": {4: "LANG-2410", 5: "LANG-2410"}, + "Microeconomics": {4: "ECON-1000", 5: "ECON-1000"}, + "Macroeconomics": {4: "ECON-1000", 5: "ECON-1000"}, + "Micro and Macroeconomics": {4: "ECON-1200", 5: "ECON-1200"}, + "English Language and Composition": {4: "WRIT-1000", 5: "WRIT-1000"}, + "English Literature and Composition": {4: "WRIT-1000", 5: "WRIT-1000"}, + "Foreign Languages": {4: "LANG-1000", 5: "LANG-1000"}, + "United States Government and Politics": {4: "STSO-1000", 5: "STSO-1000"}, + "Comparative Government and Politics": {4: "STSO-1000", 5: "STSO-1000"}, + "United States History": {4: "STSO-1000", 5: "STSO-1000"}, + "European History": {4: "STSO-1000", 5: "STSO-1000"}, + "World History": {4: "STSO-1000", 5: "STSO-1000"}, + "Human Geography": {4: "STSO-1000", 5: "STSO-1000"}, + "Music Theory": {4: "ARTS-1380", 5: "ARTS-1380"}, + "Psychology": {4: "PSYC-1200", 5: "PSYC-1200"}, + "Biology": {4: "BIOL-1010", 5: "BIOL-1010"}, + "Chemistry": {4: "CHEM-1100", 5: "CHEM-1100"}, + "Computer Science A": {4: "CSCI-1100", 5: "CSCI-1100"}, + "Computer Science Principles": {4: "CSCI-1000", 5: "CSCI-1000"}, + "Environmental Science": {4: "IENV-1000", 5: "IENV-1000"}, + "Calculus AB": {4: "Calculus I", 5: "Calculus I"}, + "Calculus BC": {4: "Calculus I and II", 5: "Calculus I and II"}, + "Physics C: Mechanics": {4: "PHYS-1100", 5: "PHYS-1100"}, + "Physics C: Electricity and Magnetism": {5: "PHYS-1200"}, + "Physics 1: Algebra-Based": {4: "PHYS-1100", 5: "PHYS-1100"}, + "Statistics": {4: "MGMT-2100", 5: "MGMT-2100"} + } QC = app_commands.Group( name="quacs", @@ -404,6 +436,16 @@ async def help_command(self, interaction: discord.Interaction): inline=False) await interaction.response.send_message(embed=embed) + @QC.command(name='ap_credit', description='Get RPI course credit for an AP subject and score') + @app_commands.describe(subject='The AP subject', score='The score received') + async def ap_credit(self, interaction: discord.Interaction, subject: str, score: int): + subject = subject.strip() + if subject in self.ap_credit_mapping and score in self.ap_credit_mapping[subject]: + course = self.ap_credit_mapping[subject][score] + await interaction.response.send_message(f"With a score of {score} in {subject}, you receive credit for {course}.", ephemeral=True) + else: + await interaction.response.send_message("No matching course found for the given subject and score.", ephemeral=True) + async def setup(bot): await bot.add_cog(RegistrationCog(bot)) \ No newline at end of file