diff --git a/.gitignore b/.gitignore index 6eb4b92..b6eae9e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -dbconfig.json -__pycache__/ \ No newline at end of file +__pycache__/ +/utils/dbconstants.py +utils/dbconstants.py diff --git a/GolosText-Regular.ttf b/assets/GolosText-Regular.ttf similarity index 100% rename from GolosText-Regular.ttf rename to assets/GolosText-Regular.ttf diff --git a/NotoSerif-Regular.ttf b/assets/NotoSerif-Regular.ttf similarity index 100% rename from NotoSerif-Regular.ttf rename to assets/NotoSerif-Regular.ttf diff --git a/Poppins-Regular.ttf b/assets/Poppins-Regular.ttf similarity index 100% rename from Poppins-Regular.ttf rename to assets/Poppins-Regular.ttf diff --git a/Roboto-Regular.ttf b/assets/Roboto-Regular.ttf similarity index 100% rename from Roboto-Regular.ttf rename to assets/Roboto-Regular.ttf diff --git a/SourceSansPro-Regular.ttf b/assets/SourceSansPro-Regular.ttf similarity index 100% rename from SourceSansPro-Regular.ttf rename to assets/SourceSansPro-Regular.ttf diff --git a/nom.ttf b/assets/nom.ttf similarity index 100% rename from nom.ttf rename to assets/nom.ttf diff --git a/bot/bot.py b/bot/bot.py new file mode 100644 index 0000000..7160c54 --- /dev/null +++ b/bot/bot.py @@ -0,0 +1,32 @@ +import nextcord +import os +from nextcord.ext import commands +from utils.utils import * +from utils.constants import TOKEN + +class Bot(commands.AutoShardedBot): + def __init__(self): + super().__init__(shard_count=1, command_prefix="nom!") + self.unloaded_cogs = [] + + def initialize(self): + self.load_extensions() + self.run(TOKEN) + + def load_extensions(self): + for cog in os.listdir("./cogs"): + if cog.endswith(".py"): + try: + self.load_extension(name=f"cogs.{cog[:-3]}") + print(f"Loaded {cog[:-3]} cog") + + except Exception as e: + print(e) + print(f"Failed to load {cog[:-3]} cog") + self.unloaded_cogs.append(cog.capitalize()[-3]) + + async def on_ready(self): + print(f"Logged in as {self.user}!") + print(self.shards) + for shard in self.shards: + await self.change_presence(status=nextcord.Status.online, activity=nextcord.Activity(type=nextcord.ActivityType.playing, name=f"Verifying | Shard: {shard+1}/{len(self.shards)}"), shard_id=shard) \ No newline at end of file diff --git a/cogs/dashboard.py b/cogs/dashboard.py new file mode 100644 index 0000000..5b846c3 --- /dev/null +++ b/cogs/dashboard.py @@ -0,0 +1,35 @@ +import nextcord +from nextcord.ext import commands +from nextcord import Interaction +from utils import check_premium, DBENDPOINT, DBNAME, DBPASS, DBUSER, generate_dashboard, create_warning_embed +from views import DashboardButtons +import pymysql + +class Dashboard(commands.Cog): + def __init__(self, client: commands.Bot): + self.client = client + + @nextcord.slash_command(name="dashboard", description="Configure the bot on the in-discord dashboard") + async def dashboard(self, interaction: Interaction): + await interaction.response.defer(with_message=True, ephemeral=True) + if not interaction.user.guild_permissions.administrator: + await interaction.send(embed=create_warning_embed(title="Insufficient permissions", description="You need the `administrator` permission to use this.")) + return + + conn = pymysql.connect(host=DBENDPOINT, port=3306, user=DBUSER, password=DBPASS, db=DBNAME) + cur = conn.cursor() + cur.execute(f"SELECT * FROM guild_configs WHERE id='{interaction.guild.id}'") + data = cur.fetchall() + if not data: + cur.execute(f"INSERT INTO guild_configs (id) VALUES ('{interaction.guild.id}')") + conn.commit() + msg = await interaction.send("Generating Dashboard...", ephemeral=True) + embed = generate_dashboard(self, data=data) + view = DashboardButtons(msg, premium=check_premium(self, guild = True, user = False, type_id=interaction.guild.id)) + await msg.edit(content="", embed=embed, view=view) + + + + +def setup(client: commands.Bot): + client.add_cog(Dashboard(client)) \ No newline at end of file diff --git a/cogs/error_handler.py b/cogs/error_handler.py new file mode 100644 index 0000000..b2838b5 --- /dev/null +++ b/cogs/error_handler.py @@ -0,0 +1,24 @@ +import nextcord +from cooldowns import CallableOnCooldown +from nextcord.ext import commands +from bot.bot import Bot +from nextcord.errors import Forbidden +from utils import * +import time + +class Error_handler(commands.Cog): + def init(self, client: Bot): + self.client = client + + @commands.Cog.listener() + async def on_application_command_error(self, interaction:nextcord.Interaction, error): + error = getattr(error, "original", error) + if isinstance(error, CallableOnCooldown): + await interaction.send(f"That command is on cooldown. You can use it again ") + elif isinstance(error, Forbidden): + await interaction.send("I don't have permission to do that") + else: + raise error + +def setup(client: Bot): + client.add_cog(Error_handler(client)) \ No newline at end of file diff --git a/cogs/extra.py b/cogs/extra.py deleted file mode 100644 index 947d570..0000000 --- a/cogs/extra.py +++ /dev/null @@ -1,56 +0,0 @@ -import nextcord -from nextcord.ext import commands, tasks -from nextcord import Interaction, SlashOption -import time, asyncio - -class BotInfoLinkButton(nextcord.ui.View): - def __init__(self): - super().__init__() - self.add_item(nextcord.ui.Button(label="Support Server", url="https://nomindustries.com/SV/support")) - self.add_item(nextcord.ui.Button(label="Invite", url="https://nomindustries.com/SV/invite")) - self.add_item(nextcord.ui.Button(label="Vote", url="https://nomindustries.com/SV/vote")) - self.add_item(nextcord.ui.Button(label="Privacy Policy", url="https://nomindustries.com/SV/privacy")) - -class PrivacyPolicyButton(nextcord.ui.View): - def __init__(self): - super().__init__() - self.add_item(nextcord.ui.Button(label="Privacy Policy", url="https://nomindustries.com/SV/privacy")) - -class Extras(commands.Cog): - - def __init__(self, client): - self.client = client - - @nextcord.slash_command(name="botinfo", description="Shows the bot information") - async def _botinfo( - self, - ctx: Interaction, - ): - await ctx.response.defer() - before = time.monotonic() - msg = await ctx.send("Loading bot information") - ping = (time.monotonic() - before) * 1000 - embed = nextcord.Embed(title="Simple Verification Bot Information", description=f"""Ping: {round(ping)}ms -Server count: {str(len(self.client.guilds))} -Support Server: [Need some support?](https://nomindustries.com/SV/invite) -Invite: [Invite Me](https://nomindustries.com/SV/invite) -Vote: [Vote for me](https://nomindustries.com/SV/vote) -Privacy; [Privacy Policy](https://nomindustries.com/SV/privacy)""", colour=0xadd8e6) - await msg.edit(content = " ", embed=embed, view=BotInfoLinkButton()) - - @nextcord.slash_command(name="guildinfo", description="[ADMIN ONLY]", guild_ids=[1015361041024155770]) - async def _guildinfo(self, ctx:Interaction): - await ctx.response.defer(ephemeral=True) - if ctx.user.id == 326065974950363136: - for i in self.client.guilds: - await ctx.user.send(len(i.members)) - await asyncio.sleep(1) - - @nextcord.slash_command(name="privacy", description=f"Get the link to our privacy policy") - async def privacy_policy(self, ctx:Interaction): - embed = nextcord.Embed(title="Privacy Policy", description="You can find our privacy policy here: \n\nhttps://nomindustries.com/sv/privacy", colour=0xadd8e6) - await ctx.send(embed=embed, view=PrivacyPolicyButton()) - - -def setup(client): - client.add_cog(Extras(client)) \ No newline at end of file diff --git a/cogs/guild_join.py b/cogs/guild_join.py deleted file mode 100644 index 1366596..0000000 --- a/cogs/guild_join.py +++ /dev/null @@ -1,33 +0,0 @@ -import nextcord -from nextcord.ext import commands, tasks -from nextcord import Interaction, SlashOption -from nextcord.abc import GuildChannel -import pymysql, json - -class GuildJoin(commands.Cog): - def __init__(self, client): - self.client = client - - @commands.Cog.listener() - async def on_guild_join(self, guild): - supguild = self.client.get_guild(1111387758028652657) - channel = supguild.get_channel(1111394323720847500) - embed = nextcord.Embed(title=f"Joined Guild", colour=0x03C04A) - embed.add_field(name=f"Guild Name (ID)", value=f"{guild.name} ({guild.id})") - embed.add_field(name=f"Guild Owner", value=f"{guild.owner} (<@{guild.owner.id}>)") - embed.add_field(name=f"Guild Members", value=f"{len(guild.members)}") - await channel.send(embed=embed) - - @commands.Cog.listener() - async def on_guild_remove(self, guild): - supguild = self.client.get_guild(1111387758028652657) - channel = supguild.get_channel(1111394323720847500) - embed = nextcord.Embed(title=f"Left Guild", colour=0x900D09) - embed.add_field(name=f"Guild Name (ID)", value=f"{guild.name} ({guild.id})") - embed.add_field(name=f"Guild Owner", value=f"{guild.owner} (<@{guild.owner.id}>)") - embed.add_field(name=f"Guild Members", value=f"{len(guild.members)}") - await channel.send(embed=embed) - - -def setup(client): - client.add_cog(cog=GuildJoin(client=client)) \ No newline at end of file diff --git a/cogs/help.py b/cogs/help.py index f2e57e0..24d0b29 100644 --- a/cogs/help.py +++ b/cogs/help.py @@ -1,44 +1,29 @@ import nextcord -from nextcord.ext import commands, tasks -from nextcord import Interaction, SlashOption -import json - - -class HelpButtons(nextcord.ui.View): - def __init__(self): - super().__init__() - self.add_item(nextcord.ui.Button(label="Support Server", url="https://discord.gg/aGzvXvTkP8")) - self.add_item(nextcord.ui.Button(label="Invite", url="https://discord.com/api/oauth2/authorize?client_id=981835181243658260&permissions=8&scope=bot%20applications.commands")) - self.add_item(nextcord.ui.Button(label="Vote", url="https://top.gg/bot/828584622156939274/vote")) - +from nextcord.ext import commands +from nextcord import Interaction +from utils.constants import COLOUR_MAIN, VOTELINK, INVITELINK, DISCORDLINK, PRIVACYLINK +from views import BotInfoLinkButton class Help(commands.Cog): - - def __init__(self, client): + def __init__(self, client: commands.Bot): self.client = client - + @nextcord.slash_command(name="help", description="Help command") - async def help(self, - ctx: Interaction): - await ctx.response.defer() - embed = nextcord.Embed(title=(f"Help"), description=(f"""Below is a list of all commands you will need:"""), colour=0xadd8e6) - embed.add_field(name=f"/config enable", value=f"""Explanation: Enable any settings you want to + async def help(self, interaction: Interaction): + await interaction.response.defer() + embed = nextcord.Embed(title=("Help"), description=("""Below is a list of all commands you will need:"""), colour=COLOUR_MAIN) + embed.add_field(name=f"/dashboard", value="""Explanation: Manage the bot's settings Requires: Administrator -Usage: ``/config enable``""") - embed.add_field(name=f"/config disable", value=f"""Explanation: Disable any settings you want to -Requires: Administrator -Usage: ``/config disable``""") +Usage: ``/dashboard``""") embed.add_field(name=f"/verifymessage", value=f"""Explanation: Send a verification message to a channel Requires: Administrator Usage: ``/verifymessage <#channel> [Custom (True/False)]``""") embed.add_field(name=f"/botinfo", value=f"""Explanation: Shows general bot info Requires: None Usage: ``/botinfo``""") - embed.add_field(name="\u200B", value=f"[Support Server](https://discord.gg/aGzvXvTkP8) | [Invite Me](https://discord.com/api/oauth2/authorize?client_id=981835181243658260&permissions=8&scope=bot%20applications.commands) | [Vote](https://top.gg/bot/828584622156939274/vote)", inline=False) - - - await ctx.send(embed=embed, view=HelpButtons()) + embed.add_field(name="\u200B", value=f"[Support Server]({DISCORDLINK}) | [Invite Me]({INVITELINK}) | [Vote]({VOTELINK}) | [Privacy Policy]({PRIVACYLINK})", inline=False) - -def setup(client): + await interaction.send(embed=embed, view=BotInfoLinkButton()) + +def setup(client: commands.Bot): client.add_cog(Help(client)) \ No newline at end of file diff --git a/cogs/misc.py b/cogs/misc.py new file mode 100644 index 0000000..1514bbc --- /dev/null +++ b/cogs/misc.py @@ -0,0 +1,83 @@ +import nextcord +from nextcord.ext import commands +import time +from nextcord import Interaction +from views import BotInfoLinkButton, PrivacyPolicyButton +import cooldowns +from utils.constants import COLOUR_MAIN, VOTELINK, INVITELINK, DISCORDLINK, PRIVACYLINK + +class Misc(commands.Cog): + def __init__(self, client: commands.Bot): + self.client = client + + @nextcord.slash_command(name="botinfo", description="Information about the bot") + async def botinfo(self, interaction: Interaction): + await interaction.response.defer() + before = time.monotonic() + msg = await interaction.send("Loading bot information") + ping = (time.monotonic() - before) * 1000 + users = 0 + for guild in self.client.guilds: + users+=guild.member_count + embed = nextcord.Embed(title="Bot Infomation", description=f"""Ping: {round(ping)}ms +Server count: {str(len(self.client.guilds))} +User count: {users:,} +Support Server: [Need some support?]({DISCORDLINK}) +Invite: [Invite Me]({INVITELINK}) +Vote: [Vote for me]({VOTELINK}) +Privacy: [Privacy Policy]({PRIVACYLINK})""", colour=COLOUR_MAIN) + await msg.edit(content = " ", embed=embed, view=BotInfoLinkButton()) + + @nextcord.slash_command(name="privacy", description="Get the link to our privacy policy") + async def privacy(self, interaction:Interaction): + await interaction.send(content="", view=PrivacyPolicyButton()) + + + @nextcord.slash_command(name="shardinfo", description="Get information on all shards") + @cooldowns.cooldown(1, 30, bucket=cooldowns.SlashBucket.author) + async def shardinfo(self, interaction:Interaction): + await interaction.response.defer() + description = "" + for shard in self.client.shards: + specshard = self.client.get_shard(shard) + shard_servers = len([guild for guild in self.client.guilds if guild.shard_id == shard]) + description+=f"\n**Shard {shard+1}** has `{round(specshard.latency*100)}ms` latency with `{shard_servers} servers`" + + if not interaction.guild: + await interaction.send(embed=nextcord.Embed(title="Simple Verification Shard Information", description=f"All current shards:\n {description}", colour=COLOUR_MAIN)) + else: + guild_shard = interaction.guild.shard_id + guildspecshard = self.client.get_shard(guild_shard) + guild_shard_servers = len([guild for guild in self.client.guilds if guild.shard_id == guild_shard]) + await interaction.send(f"This server's shard is shard **{guild_shard+1}** with `{round(guildspecshard.latency*100)}ms` and `{guild_shard_servers} servers`", embed=nextcord.Embed(title="Simple Verification Shard Information", description=f"All current shards:\n {description}", colour=COLOUR_MAIN)) + + + + @nextcord.slash_command(name="shardinfo", description="Get information on all shards") + @cooldowns.cooldown(1, 30, bucket=cooldowns.SlashBucket.author) + async def shardinfo(self, interaction:Interaction): + await interaction.response.defer() + description = "" + for shard in self.client.shards: + specshard = self.client.get_shard(shard) + shard_servers = len([guild for guild in self.client.guilds if guild.shard_id == shard]) + description+=f"\n**Shard {shard+1}** has `{round(specshard.latency*100)}ms` latency with `{shard_servers} servers`" + + if not interaction.guild: + await interaction.send(embed=nextcord.Embed(title="Simple Verification Shard Information", description=f"All current shards:\n {description}", colour=COLOUR_MAIN)) + else: + guild_shard = interaction.guild.shard_id + guildspecshard = self.client.get_shard(guild_shard) + guild_shard_servers = len([guild for guild in self.client.guilds if guild.shard_id == guild_shard]) + await interaction.send(f"This server's shard is shard **{guild_shard+1}** with `{round(guildspecshard.latency*100)}ms` and `{guild_shard_servers} servers`", embed=nextcord.Embed(title="Simple Verification Shard Information", description=f"All current shards:\n {description}", colour=COLOUR_MAIN)) + + + @nextcord.slash_command(name="debug", description="Get information useful for debugging") + @cooldowns.cooldown(1,10, bucket=cooldowns.SlashBucket.channel) + async def _debug(self, interaction:Interaction): + await interaction.response.defer() + await interaction.send(f"**Guild ID:** {interaction.guild.id}\n**Channel ID:** {interaction.channel.id}") + + +def setup(client: commands.Bot): + client.add_cog(Misc(client)) \ No newline at end of file diff --git a/cogs/premium.py b/cogs/premium.py new file mode 100644 index 0000000..08600a2 --- /dev/null +++ b/cogs/premium.py @@ -0,0 +1,135 @@ +import nextcord +import pymysql +from nextcord.ext import commands +from nextcord import Interaction, SlashOption +from utils import check_premium, create_error_embed, create_warning_embed, create_success_embed, PREMIUMLINK, DBENDPOINT, DBUSER, DBPASS, DBNAME, COLOUR_MAIN + +class Premium(commands.Cog): + def __init__(self, client: commands.Bot): + self.client = client + + @nextcord.slash_command(name="manage-premium", description="Manage your premium membership", guild_ids=[1111387758028652657, 801744339343507457]) + async def manage_premium(self, interaction: Interaction): + pass + + @manage_premium.subcommand(name="add-server", description="Add a server to your premium subscription") + async def manage_premium_add_server(self, interaction: Interaction, + guildid: str = SlashOption( + name="guild", + description="The ID of the guild to add to premium. Use /debug in your server to get this.", + required = True + )): + await interaction.response.defer() + if not check_premium(self, False, True, interaction.user.id): + await interaction.send(embed=create_error_embed(title="No premium subscription", description=f"You are not currently subscribed to any of our premium subscriptions. To purchase premium please follow [this link]({PREMIUMLINK})\n\n\nPlease Note: If you recently subscribed to premium it may take up to 30 minutes to register your subscription. If you are still unable to use this command in 30 minutes, please create a ticket in <#1111392014529990656>")) + return + + if check_premium(self, True, False, guildid): + await interaction.send(embed=create_warning_embed(title="Guild already added", description="This guild is already added to your premium subscription.")) + return + + conn = pymysql.connect(host=DBENDPOINT, port=3306, user=DBUSER, password=DBPASS, db=DBNAME) + cur = conn.cursor() + cur.execute("SELECT * FROM sv_premium_users WHERE user_id = %s", (interaction.user.id)) + data = cur.fetchall() + + servers_available = int(data[0][1]) + + cur.execute("SELECT * FROM sv_premium_guilds WHERE user_id = %s", (interaction.user.id)) + data = cur.fetchall() + + servers_used = len(data) if data else 0 + + if servers_available <= servers_used: + await interaction.send(embed=create_warning_embed(title="Max Servers Reached", description=f"You have reached the limit of premium servers that your current premium subscription allows for (``{servers_available}``). To increase this limit, upgrade your premium subscription via [this link]({PREMIUMLINK})")) + return + + try: + guild = self.client.get_guild(int(guildid)) + except: + await interaction.send(embed=create_error_embed(title="Invalid guild ID", description="You have given an invalid guild ID. Please use the `/debug` command in the guild you want to add premium to to get the correct guild ID.")) + return + + if guild is None: + await interaction.send(embed=create_error_embed(title="Invalid guild ID", description="You have given an invalid guild ID. Please use the `/debug` command in the guild you want to add premium to to get the correct guild ID.")) + return + + cur.execute("INSERT INTO sv_premium_guilds (user_id, guild_id) VALUES (%s, %s)", (str(interaction.user.id), guildid)) + conn.commit() + + await interaction.send(embed=create_success_embed(title="Premium server added", description=f"You have succesfully added `{guild.name} ({guild.id})` to your premium subscription.")) + + @manage_premium.subcommand(name="remove-server", description="Remove a server from your premium subscription") + async def manage_premium_remove_server(self, interaction: Interaction, + guildid: str = SlashOption( + name="guild", + description="The ID of the guild to remove from premium. Use /debug in your server to get this.", + required = True + )): + await interaction.response.defer() + if not check_premium(self, False, True, interaction.user.id): + await interaction.send(embed=create_error_embed(title="No premium subscription", description=f"You are not currently subscribed to any of our premium subscriptions. To purchase premium please follow [this link]({PREMIUMLINK})\n\n\nPlease Note: If you recently subscribed to premium it may take up to 30 minutes to register your subscription. If you are still unable to use this command in 30 minutes, please create a ticket in <#1111392014529990656>")) + return + + if not check_premium(self, True, False, guildid): + await interaction.send(embed=create_warning_embed(title="Guild not added", description="This guild is not currently added to your premium subscription.")) + return + + try: + guild = self.client.get_guild(int(guildid)) + except: + guild = "Unknown" + + if guild is None: + guild = "Unknown" + + conn = pymysql.connect(host=DBENDPOINT, port=3306, user=DBUSER, password=DBPASS, db=DBNAME) + cur = conn.cursor() + cur.execute("DELETE FROM sv_premium_guilds WHERE user_id=%s AND guild_id=%s", (str(interaction.user.id), guildid)) + conn.commit() + + await interaction.send(embed=create_success_embed(title="Premium server removed", description=f"You have succesfully removed `{guild.name if not guild == 'Unknown' else 'Unknown'} ({guildid})` from your premium subscription.")) + + @manage_premium.subcommand(name="view", description="View your current premium subscription") + async def manage_premium_view(self, interaction: Interaction): + await interaction.response.defer() + if not check_premium(self, False, True, interaction.user.id): + await interaction.send(embed=create_error_embed(title="No premium subscription", description=f"You are not currently subscribed to any of our premium subscriptions. To purchase premium please follow [this link]({PREMIUMLINK})\n\n\nPlease Note: If you recently subscribed to premium it may take up to 30 minutes to register your subscription. If you are still unable to use this command in 30 minutes, please create a ticket in <#1111392014529990656>")) + return + + conn = pymysql.connect(host=DBENDPOINT, port=3306, user=DBUSER, password=DBPASS, db=DBNAME) + cur = conn.cursor() + cur.execute("SELECT * FROM sv_premium_users WHERE user_id = %s", (interaction.user.id)) + data = cur.fetchall() + + servers_available = int(data[0][1]) + + cur.execute("SELECT * FROM sv_premium_guilds WHERE user_id = %s", (interaction.user.id)) + data = cur.fetchall() + + servers_used = data + + premium_role_ids = [1130511638794080419, 1130520156477608036, 1130520224689557554, 1130520278850613369, 1130520336744591431] # ACTUAL IDS = [1130511638794080419, 1130520156477608036, 1130520224689557554, 1130520278850613369, 1130520336744591431] + guild = self.client.get_guild(1111387758028652657) # ACTUAL ID = 1111387758028652657 + premium_roles = [guild.get_role(roleid) for roleid in premium_role_ids] + + user_premium_role = None + + for role in interaction.user.roles: + if role in premium_roles: + user_premium_role = role + embed=nextcord.Embed(title="Your premium subscription", description=f"Subscription: **{user_premium_role.name}**\nServers (Used Servers/Available Servers): **{len(servers_used)}/{servers_available}**", colour=COLOUR_MAIN) + servers_using = "" + + for serverid in servers_used: + try: + guild = self.client.get_guild(int(serverid[1])) + servers_using += f"**{guild.name}** ({guild.id})\n" + except: + servers_using += f"**Unknown** ({serverid[1]})" + embed.add_field(name="Servers", value=f"{servers_using}") + await interaction.send(embed=embed) + + +def setup(client: commands.Bot): + client.add_cog(Premium(client)) \ No newline at end of file diff --git a/cogs/settings.py b/cogs/settings.py deleted file mode 100644 index 5ec062b..0000000 --- a/cogs/settings.py +++ /dev/null @@ -1,184 +0,0 @@ -import nextcord -from nextcord.ext import commands, tasks -from nextcord import Interaction, SlashOption -from nextcord.abc import GuildChannel -import pymysql, json - - -transfer = { - "0" : "None", - "1" : "Choice", - "2" : "Complete" -} - - - -class Settings(commands.Cog): - - def __init__(self, client): - self.client = client - with open('dbconfig.json','r') as jsonfile: - configData = json.load(jsonfile) - self.DBUSER = configData["DBUSER"] - self.DBPASS = configData["DBPASS"] - self.DBNAME = configData["DBNAME"] - self.DBENDPOINT = configData["DBENDPOINT"] - - - - @nextcord.slash_command(name="config", description="Edit your guild settings config") - async def config(self, - ctx: Interaction - ): - pass - - @config.subcommand(name="enable", description="Enable options for your guild settings config") - async def config_enable(self, - ctx: Interaction, - verifyrole : nextcord.Role = SlashOption( - name="verifyrole", - description="The role given when users successfully verify.", - required=False - ), - unverifiedrole : nextcord.Role = SlashOption( - name="unverifiedrole", - description="The role given to users when they join the server.", - required=False - ), - logchannel : GuildChannel = SlashOption( - name="logchannel", - description="Channel to send verification logs to", - required=False, - channel_types=[nextcord.ChannelType.text] - ), - captchatype : str = SlashOption( - name="captchatype", - description="Choose the type of captcha to be sent (Use `/captchatype info` to see the different options)", - required=False, - choices={"None" : "0", "Complete" : "2"} - ), - autokick : int = SlashOption( - name="autokick", - description="Select the minimum age for an account to stay in the server (**In days**)", - required=False - ), - autocaptcha : str = SlashOption( - name="autocaptcha", - description="Enable auto captcha which will automatically start verification on new users who join the server", - required=False, - choices={"Enabled" : "enabled"} - ) - ): - global transfer - await ctx.response.defer() - conn = pymysql.connect(host=self.DBENDPOINT, port=3306, user=self.DBUSER, password=self.DBPASS, db=self.DBNAME) - cur = conn.cursor() - cur.execute(f"SELECT * FROM guild_configs WHERE id='{ctx.guild.id}'") - data = cur.fetchall() - if not data: - cur.execute(f"INSERT INTO guild_configs (id) VALUES ('{ctx.guild.id}')") - conn.commit() - conn = pymysql.connect(host=self.DBENDPOINT, port=3306, user=self.DBUSER, password=self.DBPASS, db=self.DBNAME) - cur = conn.cursor() - if not verifyrole and not unverifiedrole and not logchannel and not captchatype and not autokick and not autocaptcha: - embed=nextcord.Embed(title=f"Error", description=f"It seems you didn't select any options. Make sure you select any you want to change and try again", colour=0xadd8e6) - await ctx.send(embed=embed) - return - embed=nextcord.Embed(title=f"Guild Config Updated", description=f"You have successfully updated your guild config. A list of changes is listed below:", colour=0xadd8e6) - if verifyrole: - cur.execute(f"UPDATE guild_configs SET verifyrole='{verifyrole.id}' WHERE id='{ctx.guild.id}'") - embed.add_field(name="Verify role", value=f"{verifyrole.mention} ({verifyrole.id})") - if unverifiedrole: - cur.execute(f"UPDATE guild_configs SET unverifiedrole='{unverifiedrole.id}' WHERE id='{ctx.guild.id}'") - embed.add_field(name="Unverified role", value=f"{unverifiedrole.mention} ({unverifiedrole.id})") - if logchannel: - cur.execute(f"UPDATE guild_configs SET logchannel='{logchannel.id}' WHERE id='{ctx.guild.id}'") - embed.add_field(name="Log channel", value=f"{logchannel.mention} ({logchannel.id})") - if captchatype: - cur.execute(f"UPDATE guild_configs SET captchatype='{captchatype}' WHERE id='{ctx.guild.id}'") - captchaname = transfer[captchatype] - embed.add_field(name="Captcha type", value=f"{captchaname}") - if autokick: - if not autokick > 0: - embed.add_field(name="Auto kick", description=f"Cannot set auto kick to less than 1 day") - return - cur.execute(f"UPDATE guild_configs SET autokick='{autokick}' WHERE id='{ctx.guild.id}'") - embed.add_field(name="Auto kick", value=f"{autokick} days") - if autocaptcha: - cur.execute(f"UPDATE guild_configs SET autoveri='{autocaptcha}' WHERE id='{ctx.guild.id}'") - embed.add_field(name="Auto captcha", value=f"Auto captcha has been enabled") - conn.commit() - await ctx.send(embed=embed) - - - @config.subcommand(name="disable", description="Disable options for your guild settings config") - async def config_disable(self, - ctx: Interaction, - verifyrole : str = SlashOption( - name="verifyrole", - description="The role given when users successfully verify.", - required=False, - choices={"Disable" : "disable"} - ), - unverifiedrole : str = SlashOption( - name="unverifiedrole", - description="The role given to users when they join the server.", - required=False, - choices={"Disable" : "disable"} - ), - logchannel : str = SlashOption( - name="logchannel", - description="Channel to send verification logs to", - required=False, - choices={"Disable" : "disable"} - ), - autokick : str = SlashOption( - name="autokick", - description="Select the minimum age for an account to stay in the server (**In days**)", - required=False, - choices={"Disable" : "disable"} - ), - autocaptcha : str = SlashOption( - name="autocaptcha", - description="Enable auto captcha which will automatically start verification on new users who join the server", - required=False, - choices={"Disable" : "disable"} - ) - ): - await ctx.response.defer() - conn = pymysql.connect(host=self.DBENDPOINT, port=3306, user=self.DBUSER, password=self.DBPASS, db=self.DBNAME) - cur = conn.cursor() - cur.execute(f"SELECT * FROM guild_configs WHERE id='{ctx.guild.id}'") - data = cur.fetchall() - if not data: - cur.execute(f"INSERT INTO guild_configs (id) VALUES ('{ctx.guild.id}')") - conn.commit() - conn = pymysql.connect(host=self.DBENDPOINT, port=3306, user=self.DBUSER, password=self.DBPASS, db=self.DBNAME) - cur = conn.cursor() - if not verifyrole and not unverifiedrole and not logchannel and not autokick and not autocaptcha: - embed=nextcord.Embed(title=f"Error", description=f"It seems you didn't select any options. Make sure you select any you want to change and try again", colour=0xadd8e6) - await ctx.send(embed=embed) - return - embed=nextcord.Embed(title=f"Guild Config Updated", description=f"You have successfully updated your guild config. A list of changes is listed below:", colour=0xadd8e6) - if verifyrole: - cur.execute(f"UPDATE guild_configs SET verifyrole=NULL WHERE id='{ctx.guild.id}'") - embed.add_field(name="Verify role", value=f"None") - if unverifiedrole: - cur.execute(f"UPDATE guild_configs SET unverifiedrole=NULL WHERE id='{ctx.guild.id}'") - embed.add_field(name="Unverified role", value=f"None") - if logchannel: - cur.execute(f"UPDATE guild_configs SET logchannel=NULL WHERE id='{ctx.guild.id}'") - embed.add_field(name="Log channel", value=f"None") - if autokick: - cur.execute(f"UPDATE guild_configs SET autokick='0' WHERE id='{ctx.guild.id}'") - embed.add_field(name="Auto kick", value=f"Off") - if autocaptcha: - cur.execute(f"UPDATE guild_configs SET autoveri='no' WHERE id='{ctx.guild.id}'") - embed.add_field(name="Auto captcha", value=f"Off") - conn.commit() - await ctx.send(embed=embed) - - - -def setup(client): - client.add_cog(Settings(client)) \ No newline at end of file diff --git a/cogs/verification.py b/cogs/verification.py deleted file mode 100644 index 49a939a..0000000 --- a/cogs/verification.py +++ /dev/null @@ -1,339 +0,0 @@ -import nextcord -from nextcord.ext import commands, tasks -from nextcord import Interaction, SlashOption -from nextcord.abc import GuildChannel -import json, pymysql, asyncio, random, string, os, sys -from captcha.audio import AudioCaptcha -from captcha.image import ImageCaptcha -from views.answer_view import AnswerModal,AnswerButton -from views.embed_manager_views import EmbedCreationForm -import time -import io -import math -import datetime -from difflib import SequenceMatcher - -verifying = [] -letters = ["a ", "b ", "c ", "d ", "e ", "g ", "k ", "m ", "n ", "o ", "p ", "q ", "s ", "u ", "v ", "w ", "x ", "y ", "z "] - -class BotVerifyLinks(nextcord.ui.View): - def __init__(self): - super().__init__() - self.add_item(nextcord.ui.Button(label="Invite me to your server", url="https://nomindustries.com/SV/invite")) - self.add_item(nextcord.ui.Button(label="Privacy Policy", url="https://nomindustries.com/SV/privacy")) - - -class VerifyButton(nextcord.ui.View): - def __init__(self, client): - self.client = client - super().__init__(timeout=None) - self.add_item(nextcord.ui.Button(label="Privacy Policy", url="https://nomindustries.com/SV/privacy")) - with open('dbconfig.json','r') as jsonfile: - configData = json.load(jsonfile) - self.DBUSER = configData["DBUSER"] - self.DBPASS = configData["DBPASS"] - self.DBNAME = configData["DBNAME"] - self.DBENDPOINT = configData["DBENDPOINT"] - - async def generate_captcha_string(self): - result_str = "" - allowed = False - f = open("./filter/filter.txt", "r") - lines = f.readlines() - while not allowed: - rangee = random.randint(4,5) - result_str = ''.join(random.choice(letters) for i in range(rangee)) - allowed = True - for line in lines: - similarity = SequenceMatcher(None, (result_str.lower().replace(" ", "")), (line.strip().lower().replace(" ", ""))) - if similarity.ratio() > 0.5: - allowed=False - - return result_str - - @nextcord.ui.button(label='Verify', style=nextcord.ButtonStyle.green, custom_id='verify_button_view:verify') - async def verify(self, button: nextcord.ui.Button, ctx: nextcord.Interaction): - global verifying - if ctx.user.id in verifying: - await ctx.send("You are already verifying. Please complete that verification to verify again", ephemeral=True) - return - else: - verifying.append(ctx.user.id) - await ctx.response.defer(with_message=True, ephemeral=True) - conn = pymysql.connect(host=self.DBENDPOINT, port=3306, user=self.DBUSER, password=self.DBPASS, db=self.DBNAME) - cur = conn.cursor() - cur.execute(f"SELECT * FROM guild_configs WHERE id='{ctx.guild.id}'") - data = cur.fetchall() - if data: - data = data[0] - if not data[1] == None: - if data[4] == "0": # if captcha type is none - try: - veryroleid = data[1] - veryrole = ctx.guild.get_role(int(veryroleid)) - unverifiedrole = None - if not data[2] == None: - unverifiedroleid = data[2] - unverifiedrole = ctx.guild.get_role(int(unverifiedroleid)) - logchannel = None - if not data[3] == None: - logchannelid = data[3] - logchannel = ctx.guild.get_channel(int(logchannelid)) - if (not veryrole in ctx.user.roles) or (unverifiedrole != None and unverifiedrole in ctx.user.roles): - embed =nextcord.Embed(title="Verification", description=f"{ctx.user.mention} has successfully verified", colour=0xadd8e6) - try: - await ctx.user.add_roles(veryrole) - embed.add_field(name="Roles added", value=f"{veryrole.mention}") - if not unverifiedrole == None and unverifiedrole in ctx.user.roles: - try: - await ctx.user.remove_roles(unverifiedrole) - embed.add_field(name="Roles removed", value=f"{unverifiedrole.mention}") - except: - await ctx.send("Failed to remove unverified role.", ephemeral=True) - embed.add_field(name="Roles removed", value=f"Failed to remove unverified role") - index = verifying.index(ctx.user.id) - del verifying[index] - if not logchannel == None: - try: - await logchannel.send(embed=embed) - except: - pass - return - except Exception as e: - print(e) - await ctx.send("Failed to add verified role", ephemeral=True) - embed.add_field(name="Roles added", value=f"Failed to give verified role") - index = verifying.index(ctx.user.id) - del verifying[index] - if not logchannel == None: - try: - await logchannel.send(embed=embed) - except: - pass - return - - await ctx.send(f"You have successfully verified", ephemeral=True, view=BotVerifyLinks()) - - if not logchannel == None: - try: - await logchannel.send(embed=embed) - except: - pass - - - else: - await ctx.send("You are already verified", ephemeral=True, view=BotVerifyLinks()) - - except Exception as e: - print(e) - await ctx.send("Server config not setup properly, contact the server admins to fix this.", ephemeral=True) - - - - - elif data[4] == "2": - try: - veryroleid = data[1] - veryrole = ctx.guild.get_role(int(veryroleid)) - unverifiedrole = None - if not data[2] == None: - unverifiedroleid = data[2] - unverifiedrole = ctx.guild.get_role(int(unverifiedroleid)) - logchannel = None - if not data[3] == None: - logchannelid = data[3] - logchannel = ctx.guild.get_channel(int(logchannelid)) - if (not veryrole in ctx.user.roles) or (unverifiedrole != None and unverifiedrole in ctx.user.roles): - result_str = await self.generate_captcha_string() - image = ImageCaptcha(width = 280, height = 90, fonts=["nom.ttf", "GolosText-Regular.ttf", "NotoSerif-Regular.ttf", "Poppins-Regular.ttf", "Roboto-Regular.ttf", "SourceSansPro-Regular.ttf"], font_sizes=[60]) - data = image.generate(result_str.lower()) - bytes = io.BytesIO() - image.write(result_str, bytes) - bytes.seek(0) - embed=nextcord.Embed(title=(f"Captcha"), description=(f"""You have 1 minute to answer the captcha correctly. - -The captcha will only be **under case** **letters**. -If you get it wrong just click the verify button again and retry"""), colour=0xadd8e6) - try: - answerview = AnswerButton(actual_answer=result_str) - msg = await ctx.send(embed=embed, file=nextcord.File(bytes, f"{ctx.user.id}-captcha.jpg"), ephemeral=True) - logembed=nextcord.Embed(title=f"Verification Started", description=f"{ctx.user.mention} started verification with the captcha attached. The answer to the captcha is `{str(result_str).replace(' ', '')}`", colour=0xadd8e6) - logembed.set_author(name=f"{ctx.user}", icon_url=ctx.user.avatar.url if ctx.user.avatar else None) - logembed.set_image(url=msg.attachments[0].url) - try: - logmsg = await logchannel.send(embed=logembed) - except Exception as e: - print(e) - pass - await msg.edit(view=answerview) - await answerview.wait() - result_str = result_str.replace(" ", "") - answer = answerview.answer - await msg.delete() - if answer == "Too Long ---------------": - await ctx.send("You ran out of time to answer the captcha, please try again.", ephemeral=True) - index = verifying.index(ctx.user.id) - del verifying[index] - logembed=nextcord.Embed(title=f"Verification Failed", description=f"{ctx.user.mention} failed verification with the captcha attached. The answer to the captcha is `{result_str}` but they didn't answer in time.", colour=0xff0000) - logembed.set_author(name=f"{ctx.user}", icon_url=ctx.user.avatar.url if ctx.user.avatar else None) - try: - await logmsg.reply(embed=logembed) - except: - pass - return - if answer.lower() == result_str: - embed =nextcord.Embed(title="Verification", description=f"{ctx.user.mention} has successfully verified", colour=0x00ff00) - try: - await ctx.user.add_roles(veryrole) - embed.add_field(name="Roles added", value=f"{veryrole.mention}") - if not unverifiedrole == None and unverifiedrole in ctx.user.roles: - try: - await ctx.user.remove_roles(unverifiedrole) - embed.add_field(name="Roles removed", value=f"{unverifiedrole.mention}") - except: - await ctx.send("Failed to remove unverified role.", ephemeral=True) - embed.add_field(name="Roles removed", value=f"Failed to remove unverified role") - index = verifying.index(ctx.user.id) - del verifying[index] - if not logchannel == None: - try: - await logmsg.reply(embed=embed) - except: - pass - return - except Exception as e: - print(e) - await ctx.send("Failed to add verified role", ephemeral=True) - embed.add_field(name="Roles added", value=f"Failed to give verified role") - index = verifying.index(ctx.user.id) - del verifying[index] - if not logchannel == None: - try: - await logmsg.reply(embed=embed) - except: - pass - return - await ctx.send(f"You have successfully verified", ephemeral=True, view=BotVerifyLinks()) - - if not logchannel == None: - try: - await logmsg.reply(embed=embed) - except: - pass - else: - await ctx.send(f"Incorrect answer, please try again. Correct answer was `{result_str}`", ephemeral=True) - logembed=nextcord.Embed(title=f"Verification Failed", description=f"{ctx.user.mention} failed verification with the captcha attached. The answer to the captcha is `{result_str}` but their answer was `{answer}`", colour=0xff0000) - logembed.set_author(name=f"{ctx.user}", icon_url=ctx.user.avatar.url if ctx.user.avatar else None) - try: - await logmsg.reply(embed=logembed) - except: - pass - - except Exception as e: - print(e) - await ctx.send("Error sending captcha", ephemeral=True) - try: - os.remove(f"{ctx.user.id}-captcha.jpg") - except: - pass - else: - await ctx.send("You are already verified", ephemeral=True, view=BotVerifyLinks()) - except Exception as e: - print(e) - await ctx.send("Server config not setup properly, contact the server admins to fix this.", ephemeral=True) - - try: - index = verifying.index(ctx.user.id) - del verifying[index] - - except: - pass - - else: - await ctx.send("Server config not setup properly, contact the server admins to fix this.") - index = verifying.index(ctx.user.id) - del verifying[index] - - - -class VerifyMessage(commands.Cog): - def __init__(self, client): - self.client = client - with open('dbconfig.json','r') as jsonfile: - configData = json.load(jsonfile) - self.DBUSER = configData["DBUSER"] - self.DBPASS = configData["DBPASS"] - self.DBNAME = configData["DBNAME"] - self.DBENDPOINT = configData["DBENDPOINT"] - - @nextcord.slash_command(name="verifymessage", description="Send the verification message to a channel") - async def verifymessage(self, - ctx: Interaction, - channel: GuildChannel = SlashOption( - name="channel", - description="Channel to send verification message to", - required=True, - channel_types=[nextcord.ChannelType.text] - ), - custom: bool = SlashOption( - name="custom", - description="Choose to send a custom embed", - required=False - )): - client = self.client - if ctx.user.guild_permissions.administrator == True: - if custom: - options = EmbedCreationForm() - await ctx.response.send_modal(modal=options) - await options.wait() - embed = nextcord.Embed(title=options.embedtitle, description=options.embeddescription, colour=0xadd8e6) - else: - embed=nextcord.Embed(title=f"Verification", description=f"To verify in the server press the button below and follow the instructions from there.", colour=0xadd8e6) - try: - await channel.send(embed=embed, view=VerifyButton(self.client)) - await ctx.send(f"Message sent to {channel.mention}") - except Exception as e: - print(e) - try: - await ctx.send("Error sending message. Make sure I have permission to send messages and embed links in the selected channel.") - except: - pass - else: - await ctx.send("You require `administrator` permissions to perform this command", ephemeral=True) - - - @commands.Cog.listener() - async def on_member_join(self, member): - global verifying - conn = pymysql.connect(host=self.DBENDPOINT, port=3306, user=self.DBUSER, password=self.DBPASS, db=self.DBNAME) - cur = conn.cursor() - cur.execute(f"SELECT * FROM guild_configs WHERE id='{member.guild.id}'") - data = cur.fetchall() - if data: - data = data[0] - if not data[1] == None: - now = datetime.datetime.now(datetime.timezone.utc) - secs = (now - member.created_at).days - kicked = False - if data[5] == "0": - pass - else: - days = int(data[5]) - if secs < days: - try: - await member.send(f"You were kicked from **{member.guild.name}** due to your account age being below their limit of **{days} days**") - except: - pass - await member.kick(reason=f"Account age below server limit of {days} days") - index = verifying.index(member.id) - del verifying[index] - if not data[2] == None: - role = member.guild.get_role(int(data[2])) - await member.add_roles(role) - - - - -def setup(client): - client.add_cog(VerifyMessage(client)) - diff --git a/cogs/verify.py b/cogs/verify.py new file mode 100644 index 0000000..23ad93b --- /dev/null +++ b/cogs/verify.py @@ -0,0 +1,72 @@ +import nextcord +from nextcord.ext import commands +from nextcord import Interaction, SlashOption +from nextcord.abc import GuildChannel +from utils import DBENDPOINT, DBNAME, DBPASS, DBUSER, create_warning_embed, COLOUR_MAIN, create_success_embed, create_error_embed +from views import VerifyButton, EmbedCreator +import pymysql + +class Verify(commands.Cog): + def __init__(self, client: commands.Bot): + self.client = client + + @nextcord.slash_command(name="verifymessage", description="Send a verification message to a channel") + async def verifymessage(self, + interaction: Interaction, + channel: GuildChannel = SlashOption( + name="channel", + description="The channel you want to send the verification message to", + channel_types=[nextcord.ChannelType.text, nextcord.ChannelType.news], + required=True + ), + customembed: str = SlashOption( + name="custom-embed", + description="Do you want to customise the embed sent to the verification channel?", + choices={"Yes, I want to customise the embed": "True", "No, I don't want to customise the embed":"False"}, + required=False + )): + if not interaction.user.guild_permissions.administrator: + await interaction.send(embed=create_warning_embed(title="Insufficient permissions", description="You need the `administrator` permission to use this.")) + return + + conn = pymysql.connect(host=DBENDPOINT, port=3306, user=DBUSER, password=DBPASS, db=DBNAME) + cur = conn.cursor() + cur.execute(f"SELECT * FROM guild_configs WHERE id='{interaction.guild.id}'") + data = cur.fetchall() + if not data: + cur.execute(f"INSERT INTO guild_configs (id) VALUES ('{interaction.guild.id}')") + conn.commit() + + if (not data[1]) and (not data[2]): # Checks to see if verified roles are set. + await interaction.send(embed=create_error_embed(title="Incorrect Config!", description="You have no verified/unverified roles set, please use the `/dashboard` command to set them.")) # todo: Mention command. + conn.commit() + return + + embed=nextcord.Embed(title="Verification", description="To verify in the server press the button below and follow the instructions from there.", colour=COLOUR_MAIN) + + if customembed == "True": + options = EmbedCreator() + await interaction.response.send_modal(modal=options) + await options.wait() + embed=nextcord.Embed(title=options.embedtitle, description=options.embdeddesc, colour=COLOUR_MAIN) + + try: + await channel.send(embed=embed, view=VerifyButton(self.client)) + await interaction.send(embed=create_success_embed(title="Message sent!", description=f"I have sent your verification message to {channel.mention}")) + except: + interaction.send(embed=create_error_embed(title="Error sending verification message!", description=f"I was unable to send the verification message to {channel.mention}. Ensure I have permission to `send_messages` and `embed_links` in {channel.mention}")) + + + + @commands.Cog.listener() + async def on_ready(self): + try: + self.client.add_view(VerifyButton(self.client)) + print("Loaded VerifyButton view") + except Exception as e: + print(e) + print("Failed to load VerifyButton view") + + +def setup(client): + client.add_cog(Verify(client)) \ No newline at end of file diff --git a/main.py b/main.py index 5bbcc4a..18738d1 100644 --- a/main.py +++ b/main.py @@ -1,57 +1,8 @@ -import nextcord -import json import os -from cogs.verification import VerifyButton -from nextcord.ext import commands , tasks -import requests -os.chdir("./") - - - -class Bot(commands.Bot): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.persistent_views_added = False - self = self - - async def on_ready(self): - if not self.persistent_views_added: - self.add_view(VerifyButton(client)) - self.persistent_views_added = True - - print(f"Logged in as {client.user}!") - - await client.change_presence(status = nextcord.Status.online, activity=nextcord.Activity(type=nextcord.ActivityType.watching, name="People Verify")) - - -intents = nextcord.Intents.default() - -intents.members = True -client = Bot(command_prefix = "nom!", intents = intents) - -for filename in os.listdir('./cogs'): - if filename.endswith('.py'): - client.load_extension(f'cogs.{filename[:-3]}') - print(f"Loaded {filename} cog") - - - - - - - - - - - - - - - - - - - +from bot.bot import Bot +from utils.utils import * +os.chdir("./") -client.run("ODcxMzgwNzI0NzYxMTI0OTE0.GrrFch.XwoIEbolc2cqJh4IfqER4xlDOGoExX2wFIZANY") +disc_bot = Bot() +disc_bot.initialize() diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..bef68e4 --- /dev/null +++ b/utils/__init__.py @@ -0,0 +1,2 @@ +from .utils import * +from .constants import * \ No newline at end of file diff --git a/utils/constants.py b/utils/constants.py new file mode 100644 index 0000000..62ed349 --- /dev/null +++ b/utils/constants.py @@ -0,0 +1,14 @@ +from typing import Text, Dict + +VOTELINK: Text = "https://nomindustries.com/sv/vote" +INVITELINK: Text = "https://discord.com/api/oauth2/authorize?client_id=981835181243658260&permissions=8&scope=bot%20applications.commands" +DISCORDLINK: Text = "https://nomindustries.com/sv/support" +PRIVACYLINK: Text = "https://nomindustries.com/sv/privacy" +PREMIUMLINK: Text = "https://www.buymeacoffee.com/simpleverification/membership" + +COLOUR_MAIN = 0xadd8e6 +COLOUR_GOOD = 0x03C04A +COLOUR_NEUTRAL = 0xFCAE1E +COLOUR_BAD = 0x900D09 + + diff --git a/utils/utils.py b/utils/utils.py new file mode 100644 index 0000000..0057de0 --- /dev/null +++ b/utils/utils.py @@ -0,0 +1,46 @@ +from .constants import COLOUR_BAD, COLOUR_GOOD, COLOUR_NEUTRAL, DBENDPOINT, DBNAME, DBPASS, DBUSER, PREMIUMLINK +import nextcord, random, pymysql +from string import ascii_letters, digits + +def create_success_embed(title: str = "\u200b", description: str = "\u200b"): + embed = nextcord.Embed(title=title, description=description, color=COLOUR_GOOD) + embed.set_thumbnail(url="https://media.tenor.com/AWKzZ19awFYAAAAi/checkmark-transparent.gif") + return embed + +def create_warning_embed(title: str = "\u200b", description: str = "\u200b"): + embed = nextcord.Embed(title=title, description=description, color=COLOUR_NEUTRAL) + embed.set_thumbnail(url="https://c.tenor.com/26pNa498OS0AAAAi/warning-joypixels.gif") + return embed + +def create_error_embed(title: str = "\u200b", description: str = "\u200b"): + embed = nextcord.Embed(title=title, description=description, color=COLOUR_BAD) + embed.set_thumbnail(url="https://media.tenor.com/Gbp8h-dqDHkAAAAi/error.gif") + return embed + +def check_premium(self, guild: bool, user: bool, type_id: str) -> bool: + conn = pymysql.connect(host=DBENDPOINT, port=3306, user=DBUSER, password=DBPASS, db=DBNAME) + cur = conn.cursor() + if guild: + cur.execute(f"SELECT * FROM sv_premium_guilds WHERE guild_id='{type_id}'") + data = cur.fetchall() + return bool(data) + elif user: + cur.execute(f"SELECT * FROM sv_premium_users WHERE user_id='{type_id}'") + data = cur.fetchall() + return bool(data) + return None + +def generate_dashboard(self, data): + if data[0][6] == "no": + autov = "Disabled" + else: + autov = "Enabled" + return nextcord.Embed(title="Verification Dashboard", description=f"""Verified Role(s): {(",".join([('<@&' + i + '> ') for i in data[0][1].split(",")])) if data[0][1] else 'Not Set'}\nUnverified Role(s): {(",".join([('<@&' + i + '> ') for i in data[0][2].split(",")])) if data[0][2] else 'Not Set'} \nLog Channel: <#{data[0][3] if data[0][3] else 'Not Set'}> \nAuto Kick: {f"{data[0][5]} day(s)" if data[0][5] else 'Not Set'} \nAuto Verification ([Premium]({PREMIUMLINK}) Only): {autov}\nMinimum Captcha Length ([Premium]({PREMIUMLINK}) Only): {data[0][7]}\nMaximum Captcha Length ([Premium]({PREMIUMLINK}) Only): {data[0][8]}""") + +def generate_random_string(min_length: int = 4, max_length: int = 5): + return ''.join([random.choice(ascii_letters+digits) for i in range(min_length, max_length)]) + +def get_user_name(user) -> str: + if str(user.discriminator) != "0": + return user + return str(user.name) \ No newline at end of file diff --git a/views/__init__.py b/views/__init__.py new file mode 100644 index 0000000..c80432d --- /dev/null +++ b/views/__init__.py @@ -0,0 +1,7 @@ +from .link_button import * +from .dashboard_views import * +from .role_select import * +from .length_modal import * +from .verify_button import * +from .embed_creator import * +from .answer_button import * \ No newline at end of file diff --git a/views/answer_view.py b/views/answer_button.py similarity index 85% rename from views/answer_view.py rename to views/answer_button.py index 09b0bfa..04e1376 100644 --- a/views/answer_view.py +++ b/views/answer_button.py @@ -1,6 +1,5 @@ -import nextcord, string, io, random, os +import nextcord, io from gtts import gTTS -from captcha.audio import AudioCaptcha @@ -27,10 +26,11 @@ async def answer_ready(self, button: nextcord.ui.Button, interaction: nextcord.I async def audio_captcha(self, button: nextcord.ui.Button, interaction: nextcord.Interaction): if not self.audio_sent: answer = " ".join(letter for letter in self.actual_answer.split()) + bytes = io.BytesIO() tts = gTTS(answer) - tts.save(f"{interaction.user.id}-audio.mp3") - msg = await interaction.send(file=nextcord.File(f"{interaction.user.id}-audio.mp3", f"{interaction.user}-audio.mp3"), ephemeral=True) - os.remove(f"{interaction.user.id}-audio.mp3") + tts.save(bytes) + bytes.seek(0) + msg = await interaction.send(file=nextcord.File(bytes, f"{interaction.user}-audio.mp3"), ephemeral=True) self.audio_sent=True else: await interaction.send("You have already requested an audio captcha. Please use that to verify.", ephemeral=True) diff --git a/views/dashboard_views.py b/views/dashboard_views.py new file mode 100644 index 0000000..74fc16d --- /dev/null +++ b/views/dashboard_views.py @@ -0,0 +1,322 @@ +import nextcord, pymysql, asyncio +from nextcord.interactions import Interaction +from utils import DBENDPOINT, DBNAME, DBPASS, DBUSER, COLOUR_MAIN, create_error_embed, DISCORDLINK, create_success_embed, PREMIUMLINK, generate_dashboard +from .role_select import RoleSelect, ChannelSelect +from .length_modal import LengthModal, CaptchaLengthModal + + +class AutoVerificationButton(nextcord.ui.Button): + def __init__(self, msg, premium: bool = False): + if premium: + super().__init__(label="Toggle Auto Verification", style=nextcord.ButtonStyle.blurple, disabled=False) + else: + super().__init__(label="Toggle Auto Verification", style=nextcord.ButtonStyle.blurple, disabled=True) + self.msg = msg + + async def callback(self, interaction: Interaction): + await interaction.response.defer(with_message=True, ephemeral=True) + conn = pymysql.connect(host=DBENDPOINT, port=3306, user=DBUSER, password=DBPASS, db=DBNAME) + cur = conn.cursor() + cur.execute(f"SELECT * FROM guild_configs WHERE id='{interaction.guild.id}'") + data = cur.fetchall() + if not data: + await interaction.send(embed=create_error_embed(title="Error!", description=f"Failed to fetch your guild data, please report this in our [Support Server]({DISCORDLINK})"), view=None) + conn.commit() + self.stop() + return + + if data[0][6] == "no": + cur.execute(f"UPDATE `guild_configs` SET autoveri = 'enabled' WHERE id='{interaction.guild.id}'") + embed = create_success_embed(title="Success", description="Successfully enabled autoverification.") + else: + cur.execute(f"UPDATE `guild_configs` SET autoveri = 'no' WHERE id='{interaction.guild.id}'") + embed = create_success_embed(title="Success", description="Successfully disabled autoverification.") + + conn.commit() + conn = pymysql.connect(host=DBENDPOINT, port=3306, user=DBUSER, password=DBPASS, db=DBNAME) + cur = conn.cursor() + cur.execute(f"SELECT * FROM guild_configs WHERE id='{interaction.guild.id}'") + dataa = cur.fetchall() + eembed = generate_dashboard(self, data=dataa) + await self.msg.edit(embed=eembed) + await interaction.send(embed=create_success_embed(title="Success", description="Successfully updated your auto verification setting."), ephemeral=True) + +class MinLengthButton(nextcord.ui.Button): + def __init__(self, msg, premium: bool = False): + if premium: + super().__init__(label="Set Min Captcha Length", style=nextcord.ButtonStyle.blurple, disabled=False) + else: + super().__init__(label="Set Min Captcha Length", style=nextcord.ButtonStyle.blurple, disabled=True) + self.msg = msg + + async def callback(self, interaction: Interaction): + conn = pymysql.connect(host=DBENDPOINT, port=3306, user=DBUSER, password=DBPASS, db=DBNAME) + cur = conn.cursor() + cur.execute(f"SELECT * FROM guild_configs WHERE id='{interaction.guild.id}'") + data = cur.fetchall() + if not data: + await interaction.send(embed=create_error_embed(title="Error!", description=f"Failed to fetch your guild data, please report this in our [Support Server]({DISCORDLINK})"), view=None) + conn.commit() + self.stop() + return + + form = CaptchaLengthModal() + await interaction.response.send_modal(modal=form) + await form.wait() + + conn = pymysql.connect(host=DBENDPOINT, port=3306, user=DBUSER, password=DBPASS, db=DBNAME) + cur = conn.cursor() + cur.execute(f"SELECT * FROM guild_configs WHERE id='{interaction.guild.id}'") + data = cur.fetchall() + if not data: + await interaction.send(embed=create_error_embed(title="Error!", description=f"Failed to fetch your guild data, please report this in our [Support Server]({DISCORDLINK})"), view=None, ephemeral=True) + conn.commit() + self.stop() + return + + try: + length = int(form.captcha_length) + except: + await interaction.send(embed=create_error_embed(title="Error", description="An invalid captcha length was supplied, the captcha length must be a whole positive number"), ephemeral=True) + return + + if length < 1: + await interaction.send(embed=create_error_embed(title="Error", description="An invalid captcha length was supplied, the captcha length must be a whole positive number. (Minimum of 1)"), ephemeral=True) + return + elif length > 12: + await interaction.send(embed=create_error_embed(title="Error", description="An invalid captcha length was supplied, the captcha length must be a whole positive number. (Maximum of 12)"), ephemeral=True) + return + + cur.execute(f"UPDATE `guild_configs` SET min_captcha_len = '{length}' WHERE id='{interaction.guild.id}'") + conn.commit() + + conn = pymysql.connect(host=DBENDPOINT, port=3306, user=DBUSER, password=DBPASS, db=DBNAME) + cur = conn.cursor() + cur.execute(f"SELECT * FROM guild_configs WHERE id='{interaction.guild.id}'") + dataa = cur.fetchall() + + embed = generate_dashboard(self, data=dataa) + await self.msg.edit(embed=embed) + await interaction.send(embed=create_success_embed(title="Success", description="Successfully updated minimum captcha length."), ephemeral=True) + +class MaxLengthButton(nextcord.ui.Button): + def __init__(self, msg, premium: bool = False): + if premium: + super().__init__(label="Set Max Captcha Length", style=nextcord.ButtonStyle.blurple, disabled=False) + else: + super().__init__(label="Set Max Captcha Length", style=nextcord.ButtonStyle.blurple, disabled=True) + self.msg = msg + + async def callback(self, interaction: Interaction): + conn = pymysql.connect(host=DBENDPOINT, port=3306, user=DBUSER, password=DBPASS, db=DBNAME) + cur = conn.cursor() + cur.execute(f"SELECT * FROM guild_configs WHERE id='{interaction.guild.id}'") + data = cur.fetchall() + if not data: + await interaction.send(embed=create_error_embed(title="Error!", description=f"Failed to fetch your guild data, please report this in our [Support Server]({DISCORDLINK})"), view=None) + conn.commit() + self.stop() + return + + form = CaptchaLengthModal() + await interaction.response.send_modal(modal=form) + await form.wait() + + conn = pymysql.connect(host=DBENDPOINT, port=3306, user=DBUSER, password=DBPASS, db=DBNAME) + cur = conn.cursor() + cur.execute(f"SELECT * FROM guild_configs WHERE id='{interaction.guild.id}'") + data = cur.fetchall() + if not data: + await interaction.send(embed=create_error_embed(title="Error!", description=f"Failed to fetch your guild data, please report this in our [Support Server]({DISCORDLINK})"), view=None, ephemeral=True) + conn.commit() + self.stop() + return + + try: + length = int(form.captcha_length) + except: + await interaction.send(embed=create_error_embed(title="Error", description="An invalid captcha length was supplied, the captcha length must be a whole positive number"), ephemeral=True) + return + + if length < 1: + await interaction.send(embed=create_error_embed(title="Error", description="An invalid captcha length was supplied, the captcha length must be a whole positive number. (Minimum of 1)"), ephemeral=True) + return + elif length > 12: + await interaction.send(embed=create_error_embed(title="Error", description="An invalid captcha length was supplied, the captcha length must be a whole positive number. (Maximum of 12)"), ephemeral=True) + return + + cur.execute(f"UPDATE `guild_configs` SET max_captcha_len = '{length}' WHERE id='{interaction.guild.id}'") + conn.commit() + + conn = pymysql.connect(host=DBENDPOINT, port=3306, user=DBUSER, password=DBPASS, db=DBNAME) + cur = conn.cursor() + cur.execute(f"SELECT * FROM guild_configs WHERE id='{interaction.guild.id}'") + dataa = cur.fetchall() + + embed = generate_dashboard(self, data=dataa) + await self.msg.edit(embed=embed) + await interaction.send(embed=create_success_embed(title="Success", description="Successfully updated maximum captcha length."), ephemeral=True) + + +class DashboardButtons(nextcord.ui.View): + def __init__(self, msg, premium: bool = False): + super().__init__(timeout=300) + self.premium = premium + self.msg = msg + self.add_item(AutoVerificationButton(msg = msg, premium=premium)) + self.add_item(MinLengthButton(msg = msg, premium=premium)) + self.add_item(MaxLengthButton(msg = msg, premium=premium)) + + @nextcord.ui.button(label="Set Verification Roles", style=nextcord.ButtonStyle.blurple, disabled=False) + async def set_verification_role(self, button: nextcord.ui.Button, interaction: Interaction): + await interaction.response.defer(with_message=True, ephemeral=True) + if self.premium: + rselect = RoleSelect(minvalue=1, maxvalue=10, text="Select Verification Roles") + embed = nextcord.Embed(title="Select Verification Roles", description=f"Select the role you want members to recieve when they verify. \nAs a [premium]({PREMIUMLINK}) user you can select up to `10` roles!", color=COLOUR_MAIN) + else: + rselect = RoleSelect(minvalue=1, maxvalue=1, text="Select Verification Roles") + embed = nextcord.Embed(title="Select Verification Roles", description=f"Select the role you want members to recieve when they verify. \nAs a standard user you can select `1` role. \nUpgrade to [premium]({PREMIUMLINK}) to be able to select up to `10` roles!", color=COLOUR_MAIN) + + msg = await interaction.send(embed=embed, view=rselect, ephemeral=True) + await rselect.wait() + conn = pymysql.connect(host=DBENDPOINT, port=3306, user=DBUSER, password=DBPASS, db=DBNAME) + cur = conn.cursor() + cur.execute(f"SELECT * FROM guild_configs WHERE id='{interaction.guild.id}'") + data = cur.fetchall() + if not data: + await msg.edit(embed=create_error_embed(title="Error!", description=f"Failed to fetch your guild data, please report this in our [Support Server]({DISCORDLINK})"), view=None) + conn.commit() + self.stop() + return + + if rselect.values: + ids = ",".join([str(role.id) for role in rselect.values]) + else: + ids = None + + cur.execute(f"UPDATE `guild_configs` SET verifyrole = '{ids}' WHERE id='{interaction.guild.id}'") + conn.commit() + + await asyncio.sleep(0.3) + + conn = pymysql.connect(host=DBENDPOINT, port=3306, user=DBUSER, password=DBPASS, db=DBNAME) + cur = conn.cursor() + cur.execute(f"SELECT * FROM guild_configs WHERE id='{interaction.guild.id}'") + dataa = cur.fetchall() + + embed = generate_dashboard(self, data=dataa) + await self.msg.edit(embed=embed) + await msg.edit(embed=create_success_embed(title="Success", description="Successfully updated verification roles."), view=None) + + @nextcord.ui.button(label="Set Unverified Roles", style=nextcord.ButtonStyle.blurple, disabled=False) + async def set_unverified_role(self, button: nextcord.ui.Button, interaction: Interaction): + await interaction.response.defer(with_message=True, ephemeral=True) + if self.premium: + rselect = RoleSelect(minvalue=1, maxvalue=10, text="Select Unverified Roles") + embed = nextcord.Embed(title="Select Unverified Roles", description=f"Select the roles you want members to have while they are unverified. \nAs a [premium]({PREMIUMLINK}) user you can select up to `10` roles!", color=COLOUR_MAIN) + else: + rselect = RoleSelect(minvalue=1, maxvalue=1, text="Select Unverified Roles") + embed = nextcord.Embed(title="Select Unverified Roles", description=f"Select the roles you want members to have while they are unverified. \nAs a standard user you can select `1` role. \nUpgrade to [premium]({PREMIUMLINK}) to be able to select up to `10` roles!", color=COLOUR_MAIN) + + msg = await interaction.send(embed=embed, view=rselect, ephemeral=True) + await rselect.wait() + conn = pymysql.connect(host=DBENDPOINT, port=3306, user=DBUSER, password=DBPASS, db=DBNAME) + cur = conn.cursor() + cur.execute(f"SELECT * FROM guild_configs WHERE id='{interaction.guild.id}'") + data = cur.fetchall() + if not data: + await msg.edit(embed=create_error_embed(title="Error!", description=f"Failed to fetch your guild data, please report this in our [Support Server]({DISCORDLINK})"), view=None) + conn.commit() + self.stop() + return + + if rselect.values: + ids = ",".join([str(role.id) for role in rselect.values]) + else: + ids = None + + cur.execute(f"UPDATE `guild_configs` SET unverifiedrole = '{ids}' WHERE id='{interaction.guild.id}'") + conn.commit() + + conn = pymysql.connect(host=DBENDPOINT, port=3306, user=DBUSER, password=DBPASS, db=DBNAME) + cur = conn.cursor() + cur.execute(f"SELECT * FROM guild_configs WHERE id='{interaction.guild.id}'") + dataa = cur.fetchall() + + embed = generate_dashboard(self, data=dataa) + await self.msg.edit(embed=embed) + await msg.edit(embed=create_success_embed(title="Success", description="Successfully updated unverified roles."), view=None) + + + @nextcord.ui.button(label="Set Log Channel", style=nextcord.ButtonStyle.blurple, disabled=False) + async def set_log_channel(self, button: nextcord.ui.Button, interaction: Interaction): + await interaction.response.defer(with_message=True, ephemeral=True) + + embed = nextcord.Embed(title="Select Log Channel", description="Select the channel you want logs to be sent in. \nYou can select `1` channel.", color=COLOUR_MAIN) + cselect = ChannelSelect(minvalue=1, maxvalue=1, text="Select a Channel") + msg = await interaction.send(embed=embed, view=cselect) + await cselect.wait() + conn = pymysql.connect(host=DBENDPOINT, port=3306, user=DBUSER, password=DBPASS, db=DBNAME) + cur = conn.cursor() + cur.execute(f"SELECT * FROM guild_configs WHERE id='{interaction.guild.id}'") + data = cur.fetchall() + if not data: + await msg.edit(embed=create_error_embed(title="Error!", description=f"Failed to fetch your guild data, please report this in our [Support Server]({DISCORDLINK})"), view=None) + conn.commit() + self.stop() + return + + if cselect.values: + id = cselect.values[0] + else: + id = None + + cur.execute(f"UPDATE `guild_configs` SET logchannel = '{id.id}' WHERE id='{interaction.guild.id}'") + conn.commit() + + conn = pymysql.connect(host=DBENDPOINT, port=3306, user=DBUSER, password=DBPASS, db=DBNAME) + cur = conn.cursor() + cur.execute(f"SELECT * FROM guild_configs WHERE id='{interaction.guild.id}'") + dataa = cur.fetchall() + + embed = generate_dashboard(self, data=dataa) + await self.msg.edit(embed=embed) + await msg.edit(embed=create_success_embed(title="Success", description="Successfully updated log channel."), view=None) + + @nextcord.ui.button(label="Set Autokick Account Age", style=nextcord.ButtonStyle.blurple, disabled=False) + async def set_autokick_account_age(self, button: nextcord.ui.Button, interaction: Interaction): + form = LengthModal() + await interaction.response.send_modal(modal=form) + await form.wait() + + conn = pymysql.connect(host=DBENDPOINT, port=3306, user=DBUSER, password=DBPASS, db=DBNAME) + cur = conn.cursor() + cur.execute(f"SELECT * FROM guild_configs WHERE id='{interaction.guild.id}'") + data = cur.fetchall() + if not data: + await interaction.send(embed=create_error_embed(title="Error!", description=f"Failed to fetch your guild data, please report this in our [Support Server]({DISCORDLINK})"), view=None, ephemeral=True) + conn.commit() + self.stop() + return + + try: + length = int(form.age) + except: + await interaction.send(embed=create_error_embed(title="Error", description="An invalid duration was supplied, the duration must be a whole positive number"), ephemeral=True) + return + + if length < 0: + await interaction.send(embed=create_error_embed(title="Error", description="An invalid duration was supplied, the duration must be a whole positive number"), ephemeral=True) + return + + cur.execute(f"UPDATE `guild_configs` SET autokick = '{length}' WHERE id='{interaction.guild.id}'") + conn.commit() + + conn = pymysql.connect(host=DBENDPOINT, port=3306, user=DBUSER, password=DBPASS, db=DBNAME) + cur = conn.cursor() + cur.execute(f"SELECT * FROM guild_configs WHERE id='{interaction.guild.id}'") + dataa = cur.fetchall() + + embed = generate_dashboard(self, data=dataa) + await self.msg.edit(embed=embed) + await interaction.send(embed=create_success_embed(title="Success", description="Successfully updated autokick minimum account age."), ephemeral=True) \ No newline at end of file diff --git a/views/embed_creator.py b/views/embed_creator.py new file mode 100644 index 0000000..82f1b37 --- /dev/null +++ b/views/embed_creator.py @@ -0,0 +1,30 @@ +import nextcord + +class EmbedCreator(nextcord.ui.Modal): + def __init__(self): + super().__init__(title="Embed Creator", timeout=600) + self.embedtitle = nextcord.ui.TextInput( + label="What do you want the embed title to be?", + placeholder="You can use up to 256 characters here!", + style=nextcord.TextInputStyle.short, + min_length=1, + max_length=256, + required=True + ) + self.embeddesc = nextcord.ui.TextInput( + label="What do you want the embed description to be?", + placeholder="Tip: To mention channels dp <#channel_id> (e.g. <#1111390004783091762>)", + style=nextcord.TextInputStyle.paragraph, + min_length=0, + max_length=4000, + required=False + ) + + self.add_item(self.embedtitle) + self.add_item(self.embeddesc) + + + async def callback(self, interaction:nextcord.Interaction): + self.embedtitle = self.embedtitle.value + self.embeddesc = self.embeddesc.value + self.stop() \ No newline at end of file diff --git a/views/embed_manager_views.py b/views/embed_manager_views.py deleted file mode 100644 index c81dcf5..0000000 --- a/views/embed_manager_views.py +++ /dev/null @@ -1,33 +0,0 @@ -from asyncio import tasks -import nextcord, pymysql -from nextcord.ext import commands - - -class EmbedCreationForm(nextcord.ui.Modal): - def __init__(self): - super().__init__(title="Embed Create", timeout=None) - self.embedtitle = nextcord.ui.TextInput( - label = "What do you want the title to be?", - placeholder = "Tip: Up to 256 characters!", - style=nextcord.TextInputStyle.short, - min_length=1, - max_length=256, - required=True - ) - self.add_item(self.embedtitle) - - self.embeddescription = nextcord.ui.TextInput( - label = "What do you want the description to be?", - placeholder = "Tip: To mention channels do <#channelid> (eg. <#1028741186380365865>)", - style=nextcord.TextInputStyle.paragraph, - min_length=1, - max_length=4000, - required=True - ) - self.add_item(self.embeddescription) - - - async def callback(self, interaction: nextcord.Interaction): - self.embeddescription = self.embeddescription.value - self.embedtitle = self.embedtitle.value - self.stop() \ No newline at end of file diff --git a/views/length_modal.py b/views/length_modal.py new file mode 100644 index 0000000..8801b43 --- /dev/null +++ b/views/length_modal.py @@ -0,0 +1,40 @@ +import nextcord +from nextcord.interactions import Interaction + +class LengthModal(nextcord.ui.Modal): + def __init__(self): + super().__init__(title="Set account age", timeout=None) + + self.length = nextcord.ui.TextInput( + label = "How old should accounts be (in days)", + placeholder = "To disable autokick put 0", + style=nextcord.TextInputStyle.short, + min_length=1, + max_length=3, + required=True + ) + + self.add_item(self.length) + + async def callback(self, interaction: nextcord.Interaction): + self.age = self.length.value + self.stop() + +class CaptchaLengthModal(nextcord.ui.Modal): + def __init__(self): + super().__init__(title="Set captcha limitation", timeout=None) + + self.length = nextcord.ui.TextInput( + label = "What do you want to set the captcha limit to?", + placeholder = "", + style=nextcord.TextInputStyle.short, + min_length=1, + max_length=3, + required=True + ) + + self.add_item(self.length) + + async def callback(self, interaction: nextcord.Interaction): + self.captcha_length = self.length.value + self.stop() \ No newline at end of file diff --git a/views/link_button.py b/views/link_button.py new file mode 100644 index 0000000..7efd81b --- /dev/null +++ b/views/link_button.py @@ -0,0 +1,17 @@ +import nextcord +from utils.constants import VOTELINK, INVITELINK, DISCORDLINK, PRIVACYLINK + +class BotInfoLinkButton(nextcord.ui.View): + def __init__(self): + super().__init__() + self.add_item(nextcord.ui.Button(label="Support Server", url=DISCORDLINK)) + self.add_item(nextcord.ui.Button(label="Invite", url=INVITELINK)) + self.add_item(nextcord.ui.Button(label="Vote", url=VOTELINK)) + self.add_item(nextcord.ui.Button(label="Privacy Policy", url=PRIVACYLINK)) + + + +class PrivacyPolicyButton(nextcord.ui.View): + def __init__(self): + super().__init__() + self.add_item(nextcord.ui.Button(label="Privacy Policy", url=PRIVACYLINK)) \ No newline at end of file diff --git a/views/role_select.py b/views/role_select.py new file mode 100644 index 0000000..826439e --- /dev/null +++ b/views/role_select.py @@ -0,0 +1,40 @@ +import nextcord +from nextcord.interactions import Interaction + +class RoleSelectDropdown(nextcord.ui.RoleSelect): + def __init__(self, minvalue=1, maxvalue=1, text="Select a role"): + super().__init__(custom_id="test", placeholder=text, min_values=minvalue, max_values=maxvalue) + + async def callback(self, interaction: Interaction): + self.view.values = self.values + self.view.stop() + +class RoleSelect(nextcord.ui.View): + def __init__(self, minvalue=1, maxvalue=1, text="Select a role"): + super().__init__() + self.add_item(RoleSelectDropdown(minvalue=minvalue, maxvalue=maxvalue, text=text)) + self.values = [] + + @nextcord.ui.button(label="Remove Roles", style=nextcord.ButtonStyle.red, disabled=False) + async def remove(self, button: nextcord.ui.Button, interaction: Interaction): + self.values = None + self.stop() + +class ChannelSelectDropdown(nextcord.ui.ChannelSelect): + def __init__(self, minvalue=1, maxvalue=1, text="Select a channel"): + super().__init__(custom_id="test", placeholder=text, min_values=minvalue, max_values=maxvalue, channel_types=[nextcord.ChannelType.text, nextcord.ChannelType.news]) + + async def callback(self, interaction: nextcord.Interaction): + self.view.values = self.values + self.view.stop() + +class ChannelSelect(nextcord.ui.View): + def __init__(self, minvalue=1, maxvalue=1, text="Select a channel"): + super().__init__() + self.add_item(ChannelSelectDropdown(minvalue=minvalue, maxvalue=maxvalue, text=text)) + self.values = [] + + @nextcord.ui.button(label="Remove Channel", style=nextcord.ButtonStyle.red, disabled=False) + async def remove(self, button: nextcord.ui.Button, interaction: Interaction): + self.values = None + self.stop() \ No newline at end of file diff --git a/views/verify_button.py b/views/verify_button.py new file mode 100644 index 0000000..29dcc9f --- /dev/null +++ b/views/verify_button.py @@ -0,0 +1,145 @@ +import nextcord, pymysql, io, random +from nextcord.interactions import Interaction +from assets import * +from utils import PRIVACYLINK, create_warning_embed, DBENDPOINT, DBNAME, DBPASS, DBUSER, COLOUR_BAD, COLOUR_GOOD +from captcha.image import ImageCaptcha +from views import AnswerButton + +verifying = [] +letters = ["a ", "b ", "c ", "d ", "e ", "g ", "k ", "m ", "n ", "o ", "p ", "q ", "s ", "u ", "v ", "w ", "x ", "y ", "z "] + +@staticmethod +async def get_log_channel(guild, data): + if not data[3]: + return None + return await guild.get_channel(int(data[3])) + +@staticmethod +async def send_to_log_channel(guild, embed, data): + logchannel = await get_log_channel(guild, data) + if not logchannel: + return None + msg = await logchannel.send(embed=embed) + return msg + +@staticmethod +async def get_verified_role(guild, data): + if not data[1]: + return None + return await guild.get_role(int(data[1])) + +@staticmethod +async def get_unverified_role(guild, data): + if not data[2]: + return None + return await guild.get_role(int(data[2])) + +@staticmethod +async def add_verified_role(guild, user, data): + verifiedrole = await get_verified_role(guild, data) + if not verifiedrole: + return None + try: + await user.add_roles(verifiedrole) + return True + except: + return False + +@staticmethod +async def remove_unverified_role(guild, user, data): + unverifiedrole = await get_unverified_role(guild, data) + if not unverifiedrole: + return None + try: + await user.remove_roles(unverifiedrole) + return True + except: + return False + + +@staticmethod +async def generate_started_log_embed(user, captcha_str, captcha_image): + embed = nextcord.Embed(title="Verification Started", description=f"{user.mention} started verification with the captcha attached. The answer to the captcha is `{str(captcha_str).replace(' ', '')}`", colour=COLOUR_GOOD) + embed.set_author(name=f"{user.replace('#0', '')}", icon_url=user.avatar.url if user.avatar else None) + embed.set_image(url=captcha_image) + return embed + + +@staticmethod +async def generate_captcha_string(min_length, max_length): + return "".join(random.choice(letters) for _ in range(random.randint(min_length, max_length))) + +@staticmethod +async def generate_captcha_image(min_length, max_length): + answer_string = await generate_captcha_string(min_length, max_length) + image = ImageCaptcha(width=280, height=90, fonts=["nom.ttf", "GolosText-Regular.ttf", "NotoSerif-Regular.ttf", "Poppins-Regular.ttf", "Roboto-Regular.ttf", "SourceSansPro-Regular.ttf"], font_sizes=[60]) + data = image.generate(answer_string.lower()) + bytes = io.BytesIo() + image.write(answer_string, bytes) + bytes.seek(0) + return bytes, answer_string + +class VerifyButton(nextcord.ui.View): + def __init__(self, client): + self.client = client + super().__init__(timeout=None) + self.add_item(nextcord.ui.Button(label="Privacy Policy", url=PRIVACYLINK)) + + @nextcord.ui.button(label="Verify", style=nextcord.ButtonStyle.green, disabled=False, custom_id="verify_button") + async def verify_button(self, button: nextcord.ui.Button, interaction: Interaction): + if interaction.user.id in verifying: + await interaction.send(embed=create_warning_embed(title="Already verifying", description="You are already verifying on Simple Verification. Please complete that verification to start a new one."), ephemeral=True) + return + verifying.append(interaction.user.id) + await interaction.response.defer(with_message=True, ephemeral=True) + conn = pymysql.connect(host=DBENDPOINT, port=3306, user=DBUSER, password=DBPASS, db=DBNAME) + cur = conn.cursor() + cur.execute(f"SELECT * FROM guild_configs WHERE id='{interaction.guild.id}'") + data = cur.fetchall() + if not data or not data[1]: + await interaction.send(embed=create_warning_embed(title="Setup not complete", description="The bot is not properly configured in this server. Please talk to the server administrators to resolve this issue. (Think this is a mistake? Reach out to our support server [here](DISCORDLINK)!)"), ephemeral=True) + return + + min_captcha_length = int(data[7]) + max_captcha_length = int(data[8]) + + embed = nextcord.Embed(title="Captcha", description="You have 1 minute to complete the captcha attached. The captcha will only user **undercase** **letters**.") + captcha, answer_string = generate_captcha_image(min_captcha_length, max_captcha_length) + answerview = AnswerButton(actual_answer=answer_string) + msg = await interaction.send(embed=embed, file=nextcord.File(captcha, "captcha.jpg"), ephemeral=True) + embed=generate_started_log_embed(interaction.user.id, answer_string, msg.attachments[0].url) + logmsg = await send_to_log_channel(guild=interaction.guild, embed=embed, data=data) + await answerview.wait() + + if answerview.answer == "Too Long ---------------": + await msg.delete() + await interaction.send(embed=nextcord.Embed(title="Captcha Failed", description=f"You failed the captcha because you ran out of time. Please press the verify button to try again.", colour=COLOUR_BAD)) + return + + if not answerview.answer.lower() == answer_string.lower(): + await msg.delete() + await interaction.send(embed=nextcord.Embed(title=f"Captcha Failed", description=f"You failed the captcha because you got the answer wrong. Please press the verify button to try again.", colour=COLOUR_BAD)) + return + + error_embed = nextcord.Embed(title="Configuration Error", description="Your servers verified or unverified roles are not setup correctly. Please ensure I have the `manage_roles` permission and my highest role is **above** any of the roles you are attempting to give to users.", colour=COLOUR_BAD) + logembed = nextcord.Embed(title="Captcha Passed", description=f"{interaction.user.mention} has passed their captcha") + + + status = await remove_unverified_role(guild=interaction.guild, user=interaction.user, data=data) + if status == False: + await interaction.send(embed=create_warning_embed(title="Setup not complete", description="The bot is not properly configured in this server. Please talk to the server administrators to resolve this issue. (Think this is a mistake? Reach out to our support server [here](DISCORDLINK)!)"), ephemeral=True) + await logmsg.reply(embed=error_embed) + return + + status = await add_verified_role(guild=interaction.guild, user=interaction.user, data=data) + if status == False or status == None: + await interaction.send(embed=create_warning_embed(title="Setup not complete", description="The bot is not properly configured in this server. Please talk to the server administrators to resolve this issue. (Think this is a mistake? Reach out to our support server [here](DISCORDLINK)!)"), ephemeral=True) + await logmsg.reply(embed=error_embed) + return + + await msg.delete() + await interaction.send(embed=nextcord.Embed(title="Captcha Passed", description=f"You have successfully passed the captcha and now have access to the server.", colour=COLOUR_GOOD)) + logembed = nextcord.Embed(title="Captcha Passed", description=f"{interaction.user.mention} has passed their captcha and thri roles have been updated.") + await logmsg.reply(embed=logembed) + +