Skip to content

Commit fee1936

Browse files
authored
Merge pull request #387 from PyBotDevs/add-server-user-verification-system
Add server user verification system to let server owners require all new members to verify before using the server
2 parents 1f172b1 + 1c8c994 commit fee1936

File tree

4 files changed

+185
-2
lines changed

4 files changed

+185
-2
lines changed

cogs/serverconfig.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Imports
22
import discord
3+
import random
4+
import json
35
from discord import option, ApplicationContext
46
from discord.commands import SlashCommandGroup
57
from discord.ext import commands
@@ -12,6 +14,10 @@
1214
class ServerConfig(commands.Cog):
1315
def __init__(self, bot):
1416
self.bot = bot
17+
18+
# Load Verification Database
19+
with open("database/serververification.json", 'r', encoding="utf-8") as f:
20+
self.verification_db: dict = json.load(f)
1521

1622
serverconfig_cmds = SlashCommandGroup(name="serverconfig", description="Commands related to server customization and configuration.")
1723

@@ -65,6 +71,98 @@ async def autorole(self, ctx: ApplicationContext, channel: discord.TextChannel =
6571
)
6672
await ctx.respond(embed=localembed)
6773

74+
# Server Member Verification System
75+
@serverconfig_cmds.command(
76+
name="enable_verification",
77+
description="Enable new member verification for this server."
78+
)
79+
@option(name="verified_role", description="The role to provide to all verified members.", type=discord.Role)
80+
async def enable_verification(self, ctx: ApplicationContext, verified_role: discord.Role):
81+
"""Enable new user verification for this server."""
82+
if not ctx.author.guild_permissions.administrator:
83+
return await ctx.respond("You can't use this command! You need the `Administrator` permission to run this.", ephemeral=True)
84+
serverconf.set_verification_role(ctx.guild.id, verified_role.id)
85+
localembed = discord.Embed(
86+
title=f":white_check_mark: Server Member Verification successfully enabled for **{ctx.guild.name}**!",
87+
description=f"From now onwards, all new members will have to verify with `/verify` command, and will receive the {verified_role.mention} once verified.",
88+
color=discord.Color.green()
89+
)
90+
await ctx.respond(embed=localembed)
91+
92+
@serverconfig_cmds.command(
93+
name="disable_verification",
94+
description="Disable new member verification for this server."
95+
)
96+
async def disable_verification(self, ctx: ApplicationContext):
97+
"""Disable new member verification for this server."""
98+
if not ctx.author.guild_permissions.administrator:
99+
return await ctx.respond("You can't use this command! You need the `Administrator` permission to run this.", ephemeral=True)
100+
serverconf.set_verification_role(ctx.guild.id, None)
101+
localembed = discord.Embed(
102+
title=f":white_check_mark: Server Member Verification successfully disabled for **{ctx.guild.name}**",
103+
description=f"New members now won't have to verify in the server.",
104+
color=discord.Color.green()
105+
)
106+
await ctx.respond(embed=localembed)
107+
108+
@commands.slash_command(
109+
name="start_verification",
110+
description="Start your verification process in this server."
111+
)
112+
@commands.guild_only()
113+
async def start_verification(self, ctx: ApplicationContext):
114+
"""Start your verification process in this server."""
115+
verification_role = serverconf.fetch_verification_role(ctx.guild.id)
116+
if verification_role is None:
117+
return await ctx.respond(":warning: Verification system is disabled for this server!", ephemeral=True)
118+
if ctx.author.get_role(verification_role) is not None:
119+
return await ctx.respond(":warning: You are already verified in this server!", ephemeral=True)
120+
121+
# Construct verification data
122+
verify_code = random.randint(100000, 999999)
123+
if str(ctx.author.id) not in self.verification_db:
124+
self.verification_db[str(ctx.author.id)] = {}
125+
126+
for code in self.verification_db[str(ctx.author.id)]:
127+
if self.verification_db[str(ctx.author.id)][str(code)]["guild_id"] == ctx.guild.id:
128+
return await ctx.respond("Your verification process is already ongoing in this server!", ephemeral=True)
129+
130+
self.verification_db[str(ctx.author.id)][str(verify_code)] = {"guild_id": ctx.guild.id}
131+
with open("database/serververification.json", 'w+', encoding="utf-8") as f:
132+
json.dump(self.verification_db, f, indent=4)
133+
134+
localembed = discord.Embed(
135+
title=f"Verification for {ctx.author.name} in {ctx.guild.name} has started",
136+
description=f"Your one-time verification code is `{verify_code}`. **DO NOT share this code with anyone!**\n\nGo to isobot's DMs, and run the `/verify` command entering your verification code.",
137+
color=discord.Color.orange()
138+
)
139+
await ctx.respond(embed=localembed, ephemeral=True)
140+
141+
@commands.slash_command(
142+
name="verify",
143+
description="Enter your one-time verification code to verify membership in a server. (DM-ONLY)"
144+
)
145+
@commands.dm_only()
146+
@option(name="verification_code", description="Your one-time verification code. (6-digit number)", type=int)
147+
async def verify(self, ctx: ApplicationContext, verification_code: int):
148+
"""Enter your one-time verification code to verify membership in a server."""
149+
if str(ctx.author.id) not in self.verification_db.keys():
150+
return await ctx.respond("You are not pending verification in any servers.", ephemeral=True)
151+
if str(verification_code) not in self.verification_db[str(ctx.author.id)].keys():
152+
return await ctx.respond(":x: This verification code is invalid. Please double-check and try a different code!", ephemeral=True)
153+
154+
verification_role_id = serverconf.fetch_verification_role(self.verification_db[str(ctx.author.id)][str(verification_code)]["guild_id"])
155+
vcode_guild: discord.Guild = self.bot.get_guild(self.verification_db[str(ctx.author.id)][str(verification_code)]["guild_id"])
156+
verification_role = discord.Guild.get_role(vcode_guild, verification_role_id)
157+
server_context_user: discord.Member = vcode_guild.get_member(ctx.author.id)
158+
await server_context_user.add_roles(verification_role, reason="Member has been successfully verified in server.")
159+
160+
del self.verification_db[str(ctx.author.id)][str(verification_code)]
161+
with open("database/serververification.json", 'w+', encoding="utf-8") as f:
162+
json.dump(self.verification_db, f, indent=4)
163+
164+
return await ctx.respond(f"You have been successfully verified in **{vcode_guild.name}**!")
165+
68166
def setup(bot):
69167
bot.add_cog(ServerConfig(bot))
70168

config/commands.json

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,5 +848,75 @@
848848
"usable_by": "everyone",
849849
"disabled": false,
850850
"bugged": false
851+
},
852+
"howgay": {
853+
"name": "Gay Rating",
854+
"description": "See the gay percentage of a person!",
855+
"type": "fun",
856+
"cooldown": null,
857+
"args": null,
858+
"usable_by": "everyone",
859+
"disabled": false,
860+
"bugged": false
861+
},
862+
"serverconfig autorole": {
863+
"name": "ServerConfig Autorole",
864+
"description": "Set a role to provide to all newly-joined members of the server.",
865+
"type": "serverconfig",
866+
"cooldown": null,
867+
"args": ["role (leave blank to disable)"],
868+
"usable_by": "moderators with `manage guild` permissions",
869+
"disabled": false,
870+
"bugged": false
871+
},
872+
"serverconfig levelup_override_channel": {
873+
"name": "ServerConfig Level-up Override Channel",
874+
"description": "Set a server channel to send level-up messages to, instead of DMs.",
875+
"type": "serverconfig",
876+
"cooldown": null,
877+
"args": ["channel (leave blank to disable)"],
878+
"usable_by": "moderators with `manage guild` permissions",
879+
"disabled": false,
880+
"bugged": false
881+
},
882+
"serverconfig enable_verification": {
883+
"name": "ServerConfig Enable Verification",
884+
"description": "Enable new member verification for this server.",
885+
"type": "serverconfig",
886+
"cooldown": null,
887+
"args": ["verified_role"],
888+
"usable_by": "server admins",
889+
"disabled": false,
890+
"bugged": false
891+
},
892+
"serverconfig disable_verification": {
893+
"name": "ServerConfig Disable Verification",
894+
"description": "Disable new member verification for this server.",
895+
"type": "serverconfig",
896+
"cooldown": null,
897+
"args": null,
898+
"usable_by": "server admins",
899+
"disabled": false,
900+
"bugged": false
901+
},
902+
"start_verification": {
903+
"name": "Start Verification",
904+
"description": "Start your verification process in the server. (SERVER-ONLY)",
905+
"type": "general utilities",
906+
"cooldown": null,
907+
"args": null,
908+
"usable_by": "everyone",
909+
"disabled": false,
910+
"bugged": false
911+
},
912+
"verify": {
913+
"name": "Verify",
914+
"description": "Enter your one-time verification code to verify membership in a server. (DM-ONLY)",
915+
"type": "general utilities",
916+
"cooldown": null,
917+
"args": null,
918+
"usable_by": "everyone (in DMs)",
919+
"disabled": false,
920+
"bugged": false
851921
}
852922
}

framework/isobot/db/serverconfig.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ def generate(self, server_id: int) -> int:
3333
"channel": None,
3434
"message": None
3535
},
36-
"level_up_override_channel": None
36+
"level_up_override_channel": None,
37+
"verification_role": None
3738
}
3839
self.save(serverconf)
3940
return 0
@@ -58,6 +59,10 @@ def fetch_goodbye_message(self, server_id: int) -> dict:
5859
def fetch_levelup_override_channel(self, server_id: int) -> str:
5960
"""Fetches the level-up override channel for the specified guild. Returns `None` if not set."""
6061
return self.fetch_raw(server_id)["level_up_override_channel"]
62+
63+
def fetch_verification_role(self, server_id: int) -> str:
64+
"""Fetches the verified member role for the specified guild. Returns `None` if server verification system is disabled."""
65+
return self.fetch_raw(server_id)["verification_role"]
6166

6267
def set_autorole(self, server_id: int, role_id: int) -> int:
6368
"""Sets a role id to use as autorole for the specified guild. Returns `0` if successful."""
@@ -84,3 +89,9 @@ def set_levelup_override_channel(self, server_id: int, channel_id: int) -> int:
8489
serverconf = self.load()
8590
serverconf[str(server_id)]["level_up_override_channel"] = channel_id
8691
self.save(serverconf)
92+
93+
def set_verification_role(self, server_id: int, role_id: int) -> int:
94+
"""Sets a verified member role id for the specified guild for the specified guild, and enables server member verification. Returns `0` if successful."""
95+
serverconf = self.load()
96+
serverconf[str(server_id)]["verification_role"] = role_id
97+
self.save(serverconf)

main.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def initial_setup():
4848
"items",
4949
"levels",
5050
"serverconfig",
51+
"serververification",
5152
"warnings",
5253
"presence",
5354
"user_data",
@@ -285,6 +286,7 @@ async def on_application_command_error(ctx: ApplicationContext, error: discord.D
285286
elif isinstance(error, commands.BotMissingPermissions): await ctx.respond(":x: I don\'t have the required permissions to use this.\nIf you think this is a mistake, please go to server settings and fix isobot's role permissions.")
286287
elif isinstance(error, commands.BadBoolArgument): await ctx.respond(":x: Invalid true/false argument.", ephemeral=True)
287288
elif isinstance(error, commands.NoPrivateMessage): await ctx.respond(":x: You can only use this command in a server!", ephemeral=True)
289+
elif isinstance(error, commands.PrivateMessageOnly): await ctx.respond(":x: You can only use this command in isobot's DMs!", ephemeral=True)
288290
else:
289291
logger.error(f"Command failure: An uncaught error occured while running the command.\n >>> {error}", module="main/Client")
290292
await ctx.respond(f"An uncaught error occured while running the command. (don't worry, developers will fix this soon)\n```\n{error}\n```")
@@ -320,6 +322,7 @@ async def help_list(ctx: ApplicationContext, search: str = None):
320322
reddit_commands = str()
321323
afk_commands = str()
322324
automod_moderation_commands = str()
325+
serverconfig_commands = str()
323326
maths_commands = str()
324327
other_commands = str()
325328
for _command in commandsdb:
@@ -332,10 +335,11 @@ async def help_list(ctx: ApplicationContext, search: str = None):
332335
elif command_type == "reddit media": reddit_commands += f"`/{_command}` "
333336
elif command_type == "AFK system": afk_commands += f"`/{_command}` "
334337
elif command_type == "automod" or command_type == "moderation": automod_moderation_commands += f"`/{_command}` "
338+
elif command_type == "serverconfig": serverconfig_commands += f"`/{_command}` "
335339
elif command_type == "maths": maths_commands += f"`/{_command}` "
336340
else: other_commands += f"`/{_command}` "
337341

338-
commands_list = f"**:money_with_wings: Economy System:**\n{economy_commands}\n\n**:arrow_up: Levelling System:**\n{levelling_commands}\n\n**:toolbox: Utilities:**\n{utility_commands}\n\n**:joy: Fun Commands:**\n{fun_commands}\n\n**:crescent_moon: AFK System**\n{afk_commands}\n\n**:tools: Moderation and Automod:**\n{automod_moderation_commands}\n\n**:1234: Maths Commands:**\n{maths_commands}\n\n**:frame_photo: Reddit Media Commands:**\n{reddit_commands}\n\n**:sparkles: Miscellaneous:**\n{other_commands}"
342+
commands_list = f"**:money_with_wings: Economy System:**\n{economy_commands}\n\n**:arrow_up: Levelling System:**\n{levelling_commands}\n\n**:toolbox: Utilities:**\n{utility_commands}\n\n**:joy: Fun Commands:**\n{fun_commands}\n\n**:crescent_moon: AFK System**\n{afk_commands}\n\n**:tools: Moderation and Automod:**\n{automod_moderation_commands}\n\n**:gear: Server Configuration:**\n{serverconfig_commands}\n\n**:1234: Maths Commands:**\n{maths_commands}\n\n**:frame_photo: Reddit Media Commands:**\n{reddit_commands}\n\n**:sparkles: Miscellaneous:**\n{other_commands}"
339343
localembed = discord.Embed(title="Isobot Command Help", description=commands_list, color=color)
340344
localembed.set_footer(text="Run \"/help info\" to get more information on a command.")
341345
await ctx.respond(embed=localembed)

0 commit comments

Comments
 (0)