diff --git a/.gitignore b/.gitignore index b6e4761..77aad09 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +logs/ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/bot.py b/bot.py new file mode 100644 index 0000000..0ea6893 --- /dev/null +++ b/bot.py @@ -0,0 +1,152 @@ +import io +import os +from traceback import TracebackException +import config +import dotenv +import sys + +import logging +from logging.handlers import TimedRotatingFileHandler + +import discord +from discord.ext import commands +from discord import app_commands + +from contextlib import contextmanager, suppress + +logger = logging.getLogger("Bot") + +@contextmanager +def log_setup(): + """ + Context manager that sets up file logging + """ + try: + dotenv.load_dotenv() + logging.getLogger("discord").setLevel(logging.INFO) + logging.getLogger("discord.http").setLevel(logging.INFO) + + logger = logging.getLogger() + logger.setLevel(logging.DEBUG) + dtfmt = "%Y-%m-%d %H:%M:%S" + if not os.path.isdir("logs/"): + os.mkdir("logs/") + + # Add custom logging handlers like rich, maybe in the future?? + handlers = [ + TimedRotatingFileHandler(filename="logs/bot.log", when="d", interval=5), + logging.StreamHandler(sys.stdout) + ] + + fmt = logging.Formatter( + "[{asctime}] [{levelname:<7}] {name}: {message}", dtfmt, style="{" + ) + + for handler in handlers: + handler.setFormatter(fmt) + logger.addHandler(handler) + + yield + finally: + handlers = logger.handlers[:] + for handler in handlers: + handler.close() + logger.removeHandler(handler) + +class BotTree(app_commands.CommandTree): + """ + Subclass of app_commands.CommandTree to define the behavior for the bot's slash command tree. + Handles thrown errors within the tree and interactions between all commands + """ + + async def log_to_channel(self, interaction: discord.Interaction, err: Exception): + """ + Log error to discord channel defined in config.py + """ + + channel = await interaction.client.fetch_channel(config.DEV_LOGS_CHANNEL) + traceback_txt = "".join(TracebackException.from_exception(err).format()) + file = discord.File( + io.BytesIO(traceback_txt.encode()), + filename=f"{type(err)}.txt" + ) + + embed = discord.Embed( + title="Unhandled Exception Alert", + description=f""" + Invoked Channel: {interaction.channel.name} + \nInvoked User: {interaction.user.display_name} + \n```{traceback_txt[2000:].strip()}``` + """ + ) + + await channel.send(embed=embed, file=file) + + async def on_error( + self, interaction: discord.Interaction, error: app_commands.AppCommandError + ): + """Handles errors thrown within the command tree""" + try: + await self.log_to_channel(interaction, error) + except Exception as e: + await super().on_error(interaction, e) + + +class IITMBot(commands.AutoShardedBot): + """ + Main bot. invoked in runner (main.py) + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @classmethod + def _use_default(cls, *args): + """ + Create an instance of IITMBot with base configuration + """ + + intents = discord.Intents.all() + activity = discord.Activity( + type=discord.ActivityType.watching, name=config.DEFAULT_ACTIVITY_TEXT + ) + + x = cls( + command_prefix=config.BOT_PREFIX, + intents=intents, + owner_id=config.OWNER_ID, + activity=activity, + tree_cls=BotTree + ) + return x + + + async def load_extensions(self, *args): + for filename in os.listdir("cogs/"): + if filename.endswith(".py"): + logger.info(f"Trying to load cogs.{filename[:-3]}") + try: + await self.load_extension(f"cogs.{filename[:-3]}") + logger.info(f"Loaded cogs.{filename[:-3]}") + except Exception as e: + logger.error(f"cogs.{filename[:-3]} failed to load: {e}") + + async def close(self): + """ + Clean exit from discord and aiohttps sessions (maybe for bottle in future?) + """ + for ext in list(self.extensions): + with suppress(Exception): + await self.unload_extension(ext) + + for cog in list(self.cogs): + with suppress(Exception): + await self.remove_cog(cog) + + await super().close() + + async def on_ready(self): + logger.info("Logged in as") + logger.info(f"\tUser: {self.user.name}") + logger.info(f"\tID : {self.user.id}") + logger.info("------") diff --git a/cogs/dev.py b/cogs/dev.py new file mode 100644 index 0000000..86cd5c1 --- /dev/null +++ b/cogs/dev.py @@ -0,0 +1,158 @@ +import io +import logging +import math +import textwrap +import traceback +import logging +import config + +import discord +from discord.ext.commands.errors import ExtensionNotFound +from discord.ext import commands + +from contextlib import redirect_stdout + + +class Dev(commands.Cog): + def __init__(self, bot): + self.logger = logging.getLogger("Dev") + self.bot = bot + + @commands.Cog.listener() + async def on_ready(self): + self.logger.info("Loaded Dev") + + def cleanup_code(self, content: str): + """ + Remove code-block from eval + """ + if content.startswith("```") and content.endswith("```"): + return "\n".join(content.split("\n")[1:-1]) + + return content.strip("`\n") + + def get_syntax_error(self, e): + if e.text is None: + return f"```py\n{e.__class__.__name__}: {e}\n```" + return f'```py\n{e.text}{"^":>{e.offset}}\n{e.__class__.__name__}: {e}```' + + @commands.is_owner() + @commands.command(pass_context=True, name="eval") + async def eval(self, ctx: commands.Context, *, body: str): + """Evaluates a code""" + env = { + "bot": self.bot, + "ctx": ctx, + "channel": ctx.channel, + "author": ctx.author, + "guild": ctx.guild, + "message": ctx.message, + "self": self, + "math": math, + } + + env.update(globals()) + + body = self.cleanup_code(body) + stdout = io.StringIO() + + to_compile = f'async def func():\n{textwrap.indent(body, " ")}' + + try: + exec(to_compile, env) + except Exception as e: + return await ctx.send(f"```py\n{e.__class__.__name__}: {e}\n```") + + func = env["func"] + try: + with redirect_stdout(stdout): + ret = await func() + except Exception: + value = stdout.getvalue() + await ctx.send(f"```py\n{value}{traceback.format_exc()}\n```") + else: + value = stdout.getvalue() + try: + await ctx.message.add_reaction("\N{THUMBS UP SIGN}") + except Exception as _: + await ctx.message.add_reaction("\N{THUMBS DOWN SIGN}") + pass + + if ret is None: + self.logger.info(f"Output chars: {len(str(value))}") + if value: + if len(str(value)) >= 2000: + await ctx.send( + f"Returned over 2k chars, sending as file instead.\n" + f"(first 1.5k chars for quick reference)\n" + f"```py\n{value[0:1500]}\n```", + file=discord.File( + io.BytesIO(value.encode()), filename="output.txt" + ), + ) + else: + await ctx.send(f"```py\n{value}\n```") + else: + self.logger.info(f"Output chars: {len(str(value)) + len(str(ret))}") + self._last_result = ret + if len(str(value)) + len(str(ret)) >= 2000: + await ctx.send( + f"Returned over 2k chars, sending as file instead.\n" + f"(first 1.5k chars for quick reference)\n" + f'```py\n{f"{value}{ret}"[0:1500]}\n```', + file=discord.File( + io.BytesIO(f"{value}{ret}".encode()), filename="output.txt" + ), + ) + else: + await ctx.send(f"```py\n{value}{ret}\n```") + + @commands.is_owner() + @commands.command(name="reload", hidden=True) + async def reload(self, ctx: commands.Context, *, module_name: str): + """Reload a module""" + try: + try: + await self.bot.unload_extension(module_name) + except discord.ext.commands.errors.ExtensionNotLoaded as enl: + await ctx.send(f"Module not loaded. Trying to load it.", delete_after=6) + + await self.bot.load_extension(module_name) + await ctx.send("Module Loaded") + + except ExtensionNotFound as enf: + await ctx.send( + f"Module not found. Possibly, wrong module name provided.", + delete_after=10, + ) + except Exception as e: + self.logger.error("Unable to load module.") + self.logger.error("{}: {}".format(type(e).__name__, e)) + + @commands.command(hidden=True) + async def kill(self, ctx: commands.Context): + """Kill the bot""" + await ctx.send("Bravo 6, going dark o7") + await self.bot.close() + + @commands.command() + @commands.is_owner() + async def sync_apps(self, ctx: commands.Context): + + await ctx.bot.tree.sync(guild=discord.Object(config.PRIMARY_GUILD_ID)) + await ctx.reply("Synced local guild commands") + + @commands.command() + @commands.is_owner() + async def clear_apps(self, ctx: commands.Context): + + ctx.bot.tree.clear_commands(guild=discord.Object(config.PRIMARY_GUILD_ID)) + ctx.bot.tree.clear_commands(guild=None) + await ctx.bot.tree.sync(guild=discord.Object(config.PRIMARY_GUILD_ID)) + await ctx.bot.tree.sync() + + await ctx.send("cleared all commands") + + +async def setup(bot): + await bot.add_cog(Dev(bot)) \ No newline at end of file diff --git a/cogs/Interaction.py b/cogs/interaction.py similarity index 72% rename from cogs/Interaction.py rename to cogs/interaction.py index e48db1a..56dc571 100644 --- a/cogs/Interaction.py +++ b/cogs/interaction.py @@ -1,10 +1,15 @@ +import logging import discord +import os +import config from discord.ext import commands import re -from tools import send_email, FERNETKEY +from utils.helper import send_email from cryptography.fernet import Fernet from discord import ui +logger = logging.getLogger('Email Verification Modal') + class Verification(ui.Modal, title='Verfication Link' ): """ A UI modal that prompts the user to enter their IITMadras Roll number and @@ -32,8 +37,11 @@ async def on_submit(self, interaction: discord.Interaction): user and sends a verification email containing a unique verification link to the user's email address. """ + logger.info('on_submit') # Retrieve the Fernet Key from the config file and create a Fernet cipher - cipher = Fernet(FERNETKEY) + cipher = Fernet(os.environ.get("FERNET")) + + logger.info(cipher) # Retrieve the user's Roll number from the text input field and the user's ID from the interaction object userRoll = self.roll.value @@ -43,28 +51,27 @@ async def on_submit(self, interaction: discord.Interaction): data = userRoll+'|'+userID data = data.encode() enc = cipher.encrypt(data) - # Check if the Roll number is valid if re.fullmatch('[0-9][0-9][a-z]*[0-9]*', userRoll) and len(userRoll) in [10, 11]: # Assign the appropriate roles to the user based on their number of tries. - dotOne = discord.utils.get( - interaction.guild.roles, id=1078208692853420073) - if dotOne in interaction.user.roles: - dotTwo = discord.utils.get( - interaction.guild.roles, id=1078208892296761404) - if dotTwo in interaction.user.roles: - dotThree = discord.utils.get( - interaction.guild.roles, id=1078208973326536724) - if dotThree in interaction.user.roles: + dot_one = discord.utils.get( + interaction.guild.roles, id=config.DOT_ONE_ROLE) + if dot_one in interaction.user.roles: + dot_two = discord.utils.get( + interaction.guild.roles, id=config.DOT_TWO_ROLE) + if dot_two in interaction.user.roles: + dot_three = discord.utils.get( + interaction.guild.roles, id=config.DOT_THREE_ROLE) + if dot_three in interaction.user.roles: spam = discord.utils.get( - interaction.guild.roles, id=1078208518793994240) + interaction.guild.roles, id=config.SPAM_ROLE) await interaction.user.add_roles(spam) else: - await interaction.user.add_roles(dotThree) + await interaction.user.add_roles(dot_three) else: - await interaction.user.add_roles(dotTwo) + await interaction.user.add_roles(dot_two) else: - await interaction.user.add_roles(dotOne) + await interaction.user.add_roles(dot_one) # Send a verification email containing a unique verification link to the user's email address send_email(interaction.user.name, userRoll, enc) @@ -82,17 +89,18 @@ async def on_timeout(self, interaction: discord.Interaction) -> None: await interaction.response.send_message("We apologize, but your session has expired. Please try again and ensure that you enter your email within 5 minutes.", ephemeral=True) return - async def on_error(self, interaction: discord.Interaction, error: Exception, ) -> None: - """ - Called if there is an error while processing the user's submission. - """ - await interaction.response.send_message("We apologize for the inconvenience. Please contact a moderator and inform them about the error you encountered so that we can fix it.", ephemeral=True) - return + # async def on_error(self, interaction: discord.Interaction, error: Exception, ) -> None: + # """ + # Called if there is an error while processing the user's submission. + # """ + # await interaction.response.send_message("We apologize for the inconvenience. Please contact a moderator and inform them about the error you encountered so that we can fix it.", ephemeral=True) + # return class Interaction(commands.Cog): def __init__(self,client): self.client=client + self.logger = logging.getLogger("Interaction") @commands.Cog.listener() async def on_interaction(self,interaction: discord.Interaction): @@ -101,13 +109,14 @@ async def on_interaction(self,interaction: discord.Interaction): """ if interaction.type == discord.InteractionType.component: if interaction.data["custom_id"] == "verify_email": + self.logger.info("Interaction, button") # Get the required roles for verification and spam prevention - Qualifier = discord.utils.get( - interaction.guild.roles, id=780935056540827729) + qualifier = discord.utils.get( + interaction.guild.roles, id=config.QUALIFIER_ROLE) spam = discord.utils.get( - interaction.guild.roles, id=1078208518793994240) + interaction.guild.roles, id=config.SPAM_ROLE) # Check if user has already been verified and is not marked as a spammer - if Qualifier in interaction.user.roles: + if qualifier in interaction.user.roles: if spam not in interaction.user.roles: await interaction.response.send_modal(Verification()) else: diff --git a/cogs/Reaction.py b/cogs/reaction.py similarity index 100% rename from cogs/Reaction.py rename to cogs/reaction.py diff --git a/cogs/Slash.py b/cogs/slash.py similarity index 89% rename from cogs/Slash.py rename to cogs/slash.py index cab05ea..5da5e88 100644 --- a/cogs/Slash.py +++ b/cogs/slash.py @@ -8,8 +8,8 @@ class Slash(commands.Cog): This Class is for Slash Commands. """ - def __init__(self, client): - self.client = client + def __init__(self, bot): + self.bot = bot # Help Command (Ephemeral) @app_commands.command(name='help', description='Displays list of commands and their usage') @@ -31,5 +31,5 @@ async def help(self, interaction: discord.Interaction, command: str=None): await interaction.response.send_message(embed=output, ephemeral=True) -async def setup(client): - await client.add_cog(Slash(client)) +async def setup(bot): + await bot.add_cog(Slash(bot)) diff --git a/cogs/Message.py b/cogs/verification.py similarity index 60% rename from cogs/Message.py rename to cogs/verification.py index 7f8e004..8843d7e 100644 --- a/cogs/Message.py +++ b/cogs/verification.py @@ -1,7 +1,10 @@ +import config import discord from discord.ext import commands -from datetime import datetime -from embeds import verification_embed_dm + +from utils.helper import admin_only, verification_embed_dm + + class Menu(discord.ui.View): """ A Discord UI view that displays a menu for EMAIL VERIFICATION. @@ -15,29 +18,28 @@ def __init__(self) -> None: label="Verify", custom_id='verify_email', style=discord.ButtonStyle.blurple)) -class Message(commands.Cog): - def __init__(self,client): - self.client=client +class Verification(commands.Cog): + + def __init__(self,bot) -> None: + self.bot = bot + @commands.command() + @admin_only() + async def create(self,ctx): + await ctx.channel.send("Join our exclusive community and gain access to private channels and premium content by verifying your email address. Click the button below to complete the process and unlock all the benefits of being a part of our server.", view=Menu()) + + @commands.command() + @admin_only() + async def send(self, ctx): + embed=verification_embed_dm() + await ctx.author.send(embed=embed) @commands.Cog.listener() - async def on_message(self,message: discord.Message): - """ - An event function that triggers whenever a new message is sent in the server. It updates user roles based on the message - contents and also sends a message when a specific user sends a message. - """ - # Admin can create a new verification modal by sending a message with the prefix ^create - if message.author.id == 730762300503490650: # ID of Admin - if message.content[0:7] == '^create': - await message.channel.send("Join our exclusive community and gain access to private channels and premium content by verifying your email address. Click the button below to complete the process and unlock all the benefits of being a part of our server.", view=Menu()) - - elif message.content[0:5] =="^send": - embed=verification_embed_dm() - await message.author.send(embed=embed) - # When somes verfies there email address by clicking the verification link, the email - # verification script sends a message to the server in the format: user_id|roll|old_user - elif message.author.id == 1078142811725123594: # ID of ServerBot + async def on_message(self, message): + if message.channel.id != config.AUTOMATE_CHANNEL: + return + if message.author.id == self.bot.user.id: # ID of ServerBot data = message.content await message.delete() @@ -45,7 +47,7 @@ async def on_message(self,message: discord.Message): user_id, roll, old_user = data.split("|") user_id = int(user_id) - guild = self.client.get_guild(762774569827565569) # ID of the server + guild = self.bot.get_guild(762774569827565569) # ID of the server user = guild.get_member(user_id) # Remove all the roles from the user, except the @everyone role @@ -80,5 +82,5 @@ async def on_message(self,message: discord.Message): embed = verification_embed_dm() await user.send(embed=embed) -async def setup(client): - await client.add_cog(Message(client)) \ No newline at end of file +async def setup(bot): + await bot.add_cog(Verification(bot)) diff --git a/config.ini b/config.ini deleted file mode 100644 index 08a01af..0000000 --- a/config.ini +++ /dev/null @@ -1,9 +0,0 @@ -[BOT] -token = ********************************************************************** - -[send_in_blue] -sib_api_key = ***************************************************************************************** -sender_email = ***************** - -[Key] -fernet= ******************************************** \ No newline at end of file diff --git a/config.py b/config.py new file mode 100644 index 0000000..ec6a151 --- /dev/null +++ b/config.py @@ -0,0 +1,22 @@ +#################################### +## SERVER CONFIGURATION VARIABLES ## +#################################### +BOT_PREFIX = "!" +DEFAULT_ACTIVITY_TEXT = "Datascience tutorials" +OWNER_ID = 183092910495891467 +PRIMARY_GUILD_ID = 1104485753758687333 + + + +#################################### +## SERVER CONFIGURATION VARIABLES ## +#################################### +AUTOMATE_CHANNEL = 1104485757596475442 +DEV_LOGS_CHANNEL = 1107014275559592068 + +ADMIN_ID = 183092910495891467 +QUALIFIER_ROLE = 1104485753855156366 +DOT_ONE_ROLE = 1104485753758687338 +DOT_TWO_ROLE = 1104485753758687337 +DOT_THREE_ROLE = 1104485753758687336 +SPAM_ROLE = 1104485753758687339 diff --git a/embeds.py b/embeds.py deleted file mode 100644 index 3292ff9..0000000 --- a/embeds.py +++ /dev/null @@ -1,18 +0,0 @@ -import discord -from datetime import datetime - - -def verification_embed_dm(): - embed = discord.Embed(title="Verification Complete - You're In!", - url="https://discord.com/servers/iitm-bs-students-762774569827565569", - description="Optimize Your College Server Experience: Get Your Roles and Access Private Channels Tailored to Your Interests! Update Your Level and Get Your Club Roles and Subject Roles in Corresponding Categories. Gain Access to Private Channels and Engage with Like-minded Peers\n\n> Message from ServerBot\n```\nIf you're familiar with discord.py, you can get the server developer role. Check out my GitHub repo link and send a pull request to enhance my functionality.\n```[Github Repo](https://bit.ly/IITMbot)", - colour=0x00b0f4, - timestamp=datetime.now()) - embed.set_author(name="IITM BS Students", - url="https://discord.com/servers/iitm-bs-students-762774569827565569", - icon_url="https://cdn.discordapp.com/icons/762774569827565569/a_c1c0b26032fa931e5530abd0fbf0b14f.gif?size=128") - - embed.set_image( - url="https://i.ibb.co/mFP3KtR/833070-pxplcgyzbd-1490711385.jpg") - embed.set_footer(text="Have fun ✌️") - return embed diff --git a/main.py b/main.py index c49c4e6..06f483e 100644 --- a/main.py +++ b/main.py @@ -1,28 +1,44 @@ -from discord.ext import commands -import discord,os, tools, asyncio -from discord import app_commands +import logging +import os +import asyncio +import dotenv +from bot import IITMBot, log_setup -intents = discord.Intents.all() -client = commands.Bot(command_prefix="!", intents=intents) +# intents = discord.Intents.all() +# client = commands.Bot(command_prefix="!", intents=intents) -@client.event -async def on_ready(): - print(f"Logged in as {client.user} (ID: {client.user.id})") - try: - synced = await client.tree.sync() - print(f"Synced {len(synced)} commands") - except app_commands.CommandError as e: - print(f"Error syncing commands: {e}") +# @client.event +# async def on_ready(): +# print(f"Logged in as {client.user} (ID: {client.user.id})") +# try: +# synced = await client.tree.sync() +# print(f"Synced {len(synced)} commands") +# except app_commands.CommandError as e: +# print(f"Error syncing commands: {e}") -async def load_extensions(): - for filename in os.listdir('./cogs'): - if filename.endswith('.py'): - await client.load_extension('cogs.'+filename[:-3]) +# async def load_extensions(): +# for filename in os.listdir('./cogs'): +# if filename.endswith('.py'): +# await client.load_extension('cogs.'+filename[:-3]) + +# async def main(): +# async with client: +# await load_extensions() +# await client.start(tools.TOKEN) async def main(): - async with client: - await load_extensions() - await client.start(tools.TOKEN) + with log_setup(): + + logger = logging.getLogger("Startbot") + dotenv.load_dotenv() + bot = IITMBot._use_default() + await bot.load_extensions() + logger.info("Loaded instructions") + token = os.environ.get("DISCORD_BOT_TOKEN") + async with bot: + await bot.start(token, reconnect=True) + + if __name__ == "__main__": asyncio.run(main()) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 1dc355b..4f38e76 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,4 +18,4 @@ sib-api-v3-sdk==7.5.0 six==1.16.0 tomli==2.0.1 urllib3==1.26.14 -yarl==1.8.2 +yarl==1.8.2 \ No newline at end of file diff --git a/tools.py b/tools.py deleted file mode 100644 index c123da0..0000000 --- a/tools.py +++ /dev/null @@ -1,54 +0,0 @@ -import sib_api_v3_sdk, discord, http.client, ast,datetime -from sib_api_v3_sdk.rest import ApiException -import configparser -from itertools import cycle -from discord.ext import tasks - -# Attempt to read the configuration file and retrieve the SendinBlue API key. -try: - config = configparser.ConfigParser() - config.read('../config.ini') - SIB_API_KEY=config['send_in_blue']['sib_api_key'] -except: - config = configparser.ConfigParser() - config.read('config.ini') -SIB_API_KEY=config['send_in_blue']['sib_api_key'] - -# Attempt to read the configuration file and retrieve the Discord bot token. -TOKEN=config['BOT']['token'] -FERNETKEY = config['Key']['fernet'].encode() - -# Set the activity status for the Discord bot. -activity= cycle([discord.Activity(type=discord.ActivityType.watching,name='Donnie Darko'),discord.Activity(type=discord.ActivityType.listening,name='Traffic')]) - - -def send_email(name:str,roll_no:str,link:bytes): - """ - This function sends an email to the user with the verification link. - :param name: The name of the user. - :param roll_no: The roll number of the user. - :param link: The verification link. - :return: None - """ - # Set up the configuration for the SendinBlue API. - configuration = sib_api_v3_sdk.Configuration() - configuration.api_key['api-key'] = SIB_API_KEY - api_instance = sib_api_v3_sdk.TransactionalEmailsApi(sib_api_v3_sdk.ApiClient(configuration)) - - # Set up the email content. - senderSmtp = sib_api_v3_sdk.SendSmtpEmailSender(name="IITM Discord Server",email=config['send_in_blue']['sender_email']) - link=link.decode() - with open('email_template.txt','r') as f: - htmlcontent=f.read() - htmlcontent=htmlcontent.replace('{name}',name).replace('{link}',link) - subject="Verify Your IITM Discord Account Now" - sendTo = sib_api_v3_sdk.SendSmtpEmailTo(email=roll_no+'@ds.study.iitm.ac.in',name=name) - arrTo = [sendTo] #Adding `to` in a list - send_smtp_email = sib_api_v3_sdk.SendSmtpEmail( - sender=senderSmtp, - to=arrTo, - html_content=htmlcontent, - subject=subject) # SendSmtpEmail | Values to send a transactional email - - # Send the email using the SendinBlue API. - api_response = api_instance.send_transac_email(send_smtp_email) diff --git a/email_template.txt b/utils/email_template.txt similarity index 100% rename from email_template.txt rename to utils/email_template.txt diff --git a/utils/helper.py b/utils/helper.py new file mode 100644 index 0000000..1f74fba --- /dev/null +++ b/utils/helper.py @@ -0,0 +1,70 @@ +import os +import config + +from datetime import datetime +from itertools import cycle + +import sib_api_v3_sdk + +import discord +from discord.ext import commands + + + +# Set the activity status for the Discord bot. +activity= cycle([discord.Activity(type=discord.ActivityType.watching,name='Donnie Darko'),discord.Activity(type=discord.ActivityType.listening,name='Traffic')]) + +def admin_only(): + async def pred(ctx: commands.Context): + if ctx.author.id != config.ADMIN_ID: + raise commands.CheckFailure() + return True + return commands.check(pred) + + +def verification_embed_dm(): + embed = discord.Embed(title="Verification Complete - You're In!", + url="https://discord.com/servers/iitm-bs-students-762774569827565569", + description="Optimize Your College Server Experience: Get Your Roles and Access Private Channels Tailored to Your Interests! Update Your Level and Get Your Club Roles and Subject Roles in Corresponding Categories. Gain Access to Private Channels and Engage with Like-minded Peers\n\n> Message from ServerBot\n```\nIf you're familiar with discord.py, you can get the server developer role. Check out my GitHub repo link and send a pull request to enhance my functionality.\n```[Github Repo](https://bit.ly/IITMbot)", + colour=0x00b0f4, + timestamp=datetime.now()) + embed.set_author(name="IITM BS Students", + url="https://discord.com/servers/iitm-bs-students-762774569827565569", + icon_url="https://cdn.discordapp.com/icons/762774569827565569/a_c1c0b26032fa931e5530abd0fbf0b14f.gif?size=128") + + embed.set_image( + url="https://i.ibb.co/mFP3KtR/833070-pxplcgyzbd-1490711385.jpg") + embed.set_footer(text="Have fun ✌️") + return embed + +def send_email(name:str,roll_no:str,link:bytes): + """ + This function sends an email to the user with the verification link. + :param name: The name of the user. + :param roll_no: The roll number of the user. + :param link: The verification link. + :return: None + """ + # Set up the configuration for the SendinBlue API. + configuration = sib_api_v3_sdk.Configuration() + configuration.api_key['api-key'] =os.environ.get("SIB_API_KEY") + api_instance = sib_api_v3_sdk.TransactionalEmailsApi(sib_api_v3_sdk.ApiClient(configuration)) + + # Set up the email content. + senderSmtp = sib_api_v3_sdk.SendSmtpEmailSender(name="IITM Discord Server",email=os.environ.get("SIB_SENDER_EMAIL")) + link=link.decode() + print(link) + with open('utils/email_template.txt','r') as f: + htmlcontent=f.read() + htmlcontent=htmlcontent.replace('{name}',name).replace('{link}',link) + subject="Verify Your IITM Discord Account Now" + sendTo = sib_api_v3_sdk.SendSmtpEmailTo(email=roll_no+'@ds.study.iitm.ac.in',name=name) + arrTo = [sendTo] #Adding `to` in a list + send_smtp_email = sib_api_v3_sdk.SendSmtpEmail( + sender=senderSmtp, + to=arrTo, + html_content=htmlcontent, + subject=subject) # SendSmtpEmail | Values to send a transactional email + + # Send the email using the SendinBlue API. + api_response = api_instance.send_transac_email(send_smtp_email)