-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement admin only commands and make some commands admin-able #18
Changes from all commits
7f8f417
76d1f99
41e409a
3f8fbb6
39fcfb5
380e75f
82c4599
dfad23a
06e8b2a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -6,6 +6,8 @@ | |||||||||||
|
||||||||||||
- `get_users_from_discordid()`: Find users from DB given user ID | ||||||||||||
- `is_verified()`: If a user is present in DB or not | ||||||||||||
- `is_bot_admin()`: If a user is a bot admin or not | ||||||||||||
- `is_author_bot_admin()`: If the author of the current message is a bot admin or not | ||||||||||||
- `get_realname_from_discordid()`: Get a user's real name from their Discord ID. | ||||||||||||
- `send_link()`: Send link for reattempting authentication. | ||||||||||||
- `create_roles_if_missing()`: Adds missing roles to a server. | ||||||||||||
|
@@ -15,11 +17,11 @@ | |||||||||||
- `post_verification()`: Handle role add/delete and nickname set post-verification of given user. | ||||||||||||
- `verify_user()`: Implements `.verify`. | ||||||||||||
- `backend_info()`: Logs server details for debug purposes | ||||||||||||
- `is_academic()`: Checks if server is for academic use. | ||||||||||||
- `is_academic_or_bot_admin()`: Checks if server is for academic use or if the author is a bot admin. | ||||||||||||
- `query()`: Returns user details, uses Discord ID to find in DB. | ||||||||||||
- `query_error()`: Replies eror message if server is not academic. | ||||||||||||
- `query_error()`: Replies eror message if server is not academic or author is not a bot admin. | ||||||||||||
- `roll()`: Returns user details, uses roll number to find in DB. | ||||||||||||
- `roll_error()`: Replies eror message if server is not academic. | ||||||||||||
- `roll_error()`: Replies eror message if server is not academic or author is not a bot admin. | ||||||||||||
- `on_ready()`: Logs a message when the bot joins a server. | ||||||||||||
- `main()`: Reads server config, loads DB and starts bot. | ||||||||||||
|
||||||||||||
|
@@ -57,6 +59,8 @@ | |||||||||||
SUBPATH = os.getenv("SUBPATH") | ||||||||||||
BASE_URL = f"{PROTOCOL}://{HOST}{_PORT_AS_SUFFIX}{SUBPATH}" | ||||||||||||
|
||||||||||||
BOT_ADMINS = [int(id) for id in os.getenv("BOT_ADMINS").split(",") if id != ""] | ||||||||||||
|
||||||||||||
intent = discord.Intents.default() | ||||||||||||
intent.message_content = True | ||||||||||||
bot = commands.Bot(command_prefix=".", intents=intent) | ||||||||||||
|
@@ -73,6 +77,15 @@ class DBEntry(TypedDict): | |||||||||||
view: bool | ||||||||||||
|
||||||||||||
|
||||||||||||
class CheckFailedException(commands.CommandError): | ||||||||||||
"""Custom exception to help identify which check function failed.""" | ||||||||||||
|
||||||||||||
def __init__(self, check_name: str): | ||||||||||||
"""Initialise with the name of the check.""" | ||||||||||||
super().__init__() | ||||||||||||
self.check_name = check_name | ||||||||||||
|
||||||||||||
|
||||||||||||
def get_users_from_discordid(user_id: int): | ||||||||||||
""" | ||||||||||||
Finds users from the database, given their ID and returns | ||||||||||||
|
@@ -89,6 +102,20 @@ def is_verified(user_id: int): | |||||||||||
return True if get_users_from_discordid(user_id) else False | ||||||||||||
|
||||||||||||
|
||||||||||||
def is_bot_admin(user_id: int): | ||||||||||||
"""Checks if the user with the given discord ID is a bot admin or not.""" | ||||||||||||
return user_id in BOT_ADMINS | ||||||||||||
|
||||||||||||
|
||||||||||||
def is_author_bot_admin(ctx: commands.Context): | ||||||||||||
"""Wrapper check function for `is_bot_admin`; checks if the user who invoked the command | ||||||||||||
is a bot admin or not.""" | ||||||||||||
author = ctx.message.author | ||||||||||||
if not is_bot_admin(author.id): | ||||||||||||
raise CheckFailedException("is_author_bot_admin") | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
return True | ||||||||||||
|
||||||||||||
|
||||||||||||
def get_realname_from_discordid(user_id: int): | ||||||||||||
"""Returns the real name of the first user who matches the given ID.""" | ||||||||||||
users = get_users_from_discordid(user_id) | ||||||||||||
|
@@ -221,8 +248,10 @@ async def verify_user(ctx: commands.Context): | |||||||||||
|
||||||||||||
|
||||||||||||
@bot.hybrid_command(name="backend_info") | ||||||||||||
@commands.check(is_author_bot_admin) | ||||||||||||
async def backend_info(ctx: commands.Context): | ||||||||||||
"""For debugging server info; sends details of the server.""" | ||||||||||||
|
||||||||||||
uname = platform.uname() | ||||||||||||
await ctx.reply( | ||||||||||||
f"Here are the server details:\n" | ||||||||||||
|
@@ -235,27 +264,45 @@ async def backend_info(ctx: commands.Context): | |||||||||||
) | ||||||||||||
|
||||||||||||
|
||||||||||||
def is_academic(ctx: commands.Context): | ||||||||||||
"""Checks if the server is an academic server.""" | ||||||||||||
@backend_info.error | ||||||||||||
async def backend_info_error(ctx: commands.Context, error: Exception): | ||||||||||||
"""If the author of the message is not a bot admin then reply accordingly.""" | ||||||||||||
if ( | ||||||||||||
isinstance(error, CheckFailedException) | ||||||||||||
and error.check_name == "is_author_bot_admin" | ||||||||||||
): | ||||||||||||
Comment on lines
+270
to
+273
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
author = ctx.message.author | ||||||||||||
await ctx.reply(f"{author.mention} is not a bot admin.", ephemeral=True) | ||||||||||||
else: | ||||||||||||
await ctx.reply("Some checks failed.", ephemeral=True) | ||||||||||||
|
||||||||||||
|
||||||||||||
def is_academic_or_bot_admin(ctx: commands.Context): | ||||||||||||
"""Checks if the server is an academic server or if the author is a bot admin.""" | ||||||||||||
if ctx.guild is None: | ||||||||||||
return False | ||||||||||||
|
||||||||||||
try: | ||||||||||||
return server_configs[ctx.guild.id]["is_academic"] | ||||||||||||
except KeyError: | ||||||||||||
return False | ||||||||||||
author = ctx.message.author | ||||||||||||
is_admin = is_bot_admin(author.id) | ||||||||||||
if not server_configs[ctx.guild.id]["is_academic"] and not is_admin: | ||||||||||||
raise CheckFailedException("is_academic_or_bot_admin") | ||||||||||||
return True | ||||||||||||
except KeyError as err: | ||||||||||||
raise CheckFailedException("server_config") from err | ||||||||||||
Comment on lines
+286
to
+292
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be better to leave this function as it is, as def is_academic_or_bot_admin(ctx: commands.Context):
"""Checks if the server is an academic server or if the author is a bot admin."""
return is_author_bot_admin(ctx) or is_academic(ctx) Of course, you would need to modify There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just realised that |
||||||||||||
|
||||||||||||
|
||||||||||||
@bot.hybrid_command(name="query") | ||||||||||||
@commands.check(is_academic) | ||||||||||||
@commands.check(is_academic_or_bot_admin) | ||||||||||||
async def query( | ||||||||||||
ctx: commands.Context, | ||||||||||||
identifier: discord.User, | ||||||||||||
): | ||||||||||||
""" | ||||||||||||
First checks if the server is an academic one. If so, finds the user who invoked the | ||||||||||||
command (by Discord ID) in the DB. If present, replies with their name, email and | ||||||||||||
roll number. Otherwise replies telling the user they are not registed with CAS. | ||||||||||||
First checks if the server is an academic one or if the author is a bot admin. | ||||||||||||
If so, finds the user mentioned (by Discord ID) in the command in the DB. | ||||||||||||
If present, replies with their name, email and roll number. Otherwise | ||||||||||||
replies telling the author that the mentioned user is not registed with CAS. | ||||||||||||
""" | ||||||||||||
if db is None: | ||||||||||||
await ctx.reply( | ||||||||||||
|
@@ -280,24 +327,37 @@ async def query( | |||||||||||
@query.error | ||||||||||||
async def query_error(ctx: commands.Context, error: Exception): | ||||||||||||
""" | ||||||||||||
For the `query` command, if the server is not academic, replies with error message. | ||||||||||||
For the `query` command, if the server is not academic and if the author is | ||||||||||||
not a bot admin, replies with an error message. | ||||||||||||
""" | ||||||||||||
if isinstance(error, commands.CheckFailure): | ||||||||||||
await ctx.reply("This server is not for academic purposes.", ephemeral=True) | ||||||||||||
if ( | ||||||||||||
isinstance(error, CheckFailedException) | ||||||||||||
and error.check_name == "is_academic_or_bot_admin" | ||||||||||||
): | ||||||||||||
Comment on lines
+333
to
+336
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
author = ctx.message.author | ||||||||||||
await ctx.reply( | ||||||||||||
"This server is not for academic purposes " | ||||||||||||
f"and {author.mention} is not a bot admin.", | ||||||||||||
ephemeral=True, | ||||||||||||
) | ||||||||||||
else: | ||||||||||||
await ctx.reply("Some checks failed.", ephemeral=True) | ||||||||||||
|
||||||||||||
|
||||||||||||
@bot.hybrid_command(name="roll") | ||||||||||||
@commands.check(is_academic) | ||||||||||||
@commands.check(is_academic_or_bot_admin) | ||||||||||||
async def roll( | ||||||||||||
ctx: commands.Context, | ||||||||||||
identifier: int, | ||||||||||||
): | ||||||||||||
""" | ||||||||||||
First checks if the server is an academic one. If so, finds the user who invoked the | ||||||||||||
command in the DB. If present, replies with their name, email and | ||||||||||||
roll number. Otherwise replies telling the user they are not registed with CAS. | ||||||||||||
First checks if the server is an academic one or if the author is a bot admin. | ||||||||||||
If so, finds the user mentioned in the command in the DB. If present, replies | ||||||||||||
with their name, email and roll number. Otherwise replies telling the author | ||||||||||||
that the mentioned user is not registed with CAS. | ||||||||||||
|
||||||||||||
Same as the `query` command, except this searches by roll number instead of Discord ID. | ||||||||||||
Same as the `query` command, except the user is mentioned by roll number | ||||||||||||
instead of Discord ID. | ||||||||||||
""" | ||||||||||||
if db is None: | ||||||||||||
await ctx.reply( | ||||||||||||
|
@@ -322,10 +382,21 @@ async def roll( | |||||||||||
@roll.error | ||||||||||||
async def roll_error(ctx: commands.Context, error: Exception): | ||||||||||||
""" | ||||||||||||
For the `roll` command, if the server is not academic, replies with error message. | ||||||||||||
For the `roll` command, if the server is not academic and if the author is | ||||||||||||
not a bot admin, replies with an error message. | ||||||||||||
""" | ||||||||||||
if isinstance(error, commands.CheckFailure): | ||||||||||||
await ctx.reply("This server is not for academic purposes.", ephemeral=True) | ||||||||||||
author = ctx.message.author | ||||||||||||
if ( | ||||||||||||
isinstance(error, CheckFailedException) | ||||||||||||
and error.check_name == "is_academic_or_bot_admin" | ||||||||||||
): | ||||||||||||
Comment on lines
+389
to
+392
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
await ctx.reply( | ||||||||||||
"This server is not for academic purposes " | ||||||||||||
f"and {author.mention} is not a bot admin.", | ||||||||||||
ephemeral=True, | ||||||||||||
) | ||||||||||||
else: | ||||||||||||
await ctx.reply("Some checks failed.", ephemeral=True) | ||||||||||||
|
||||||||||||
|
||||||||||||
@bot.event | ||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of storing the "exception type as a string" I would suggest a more OOP-like approach
This way
isinstance(error, CheckAdmin)
can detect admin command failure,isinstance(error, CheckAcademic)
can detect academic server error, andisinstance(error, CheckAdminOrAcademic)
can be used to detect either of above