Skip to content

Commit

Permalink
Merge pull request #18 from royce-mathew/master
Browse files Browse the repository at this point in the history
Bug Fixes, Moderation Logs, New Help Command
  • Loading branch information
royce-mathew authored Dec 27, 2022
2 parents 88dce2e + 978b51e commit f8e96e4
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 25 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ Commands | Details
## Other Commands
Commands | Details
------------ | -------------
**help** | Display all commands
**help** `command_name` | Display all commands. Adding optional `command_name` lets you get help for the specified command
**getfact** | Get a random fact of the day
**personalitytest** | Test the personality of a user, saves the userdata after the user run's the command the first time
**covidstats** | Get the Covid-19 Statistics and compare it to when the last time the command was ran
Expand Down
64 changes: 64 additions & 0 deletions cogs/ErrorHandler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import discord
from discord.ext import commands
import sys
import traceback


# Credit to https://gist.github.com/EvieePy for creating this method of Exception handling through one Cog
class ErrorHandler(commands.Cog):
def __init__(self, client: discord.Client):
self.client = client

@commands.Cog.listener()
async def on_command_error(self, ctx: commands.Context, error):
"""The event triggered when an error is raised while invoking a command.
Parameters
------------
ctx: commands.Context
The context used for command invocation.
error: commands.CommandError
The Exception raised.
"""

# This prevents any commands with local handlers being handled here in on_command_error.
if hasattr(ctx.command, 'on_error'):
return

# This prevents any cogs with an overwritten cog_command_error being handled here.
cog = ctx.cog
if cog:
if cog._get_overridden_method(cog.cog_command_error) is not None:
return

ignored = (commands.CommandNotFound, )

# Allows us to check for original exceptions raised and sent to CommandInvokeError.
# If nothing is found. We keep the exception passed to on_command_error.
error = getattr(error, 'original', error)

# Anything in ignored will return and prevent anything happening.
if isinstance(error, ignored):
return

if isinstance(error, commands.DisabledCommand):
await ctx.send(f'{ctx.command} has been disabled.')

elif isinstance(error, commands.NoPrivateMessage):
try:
await ctx.author.send(f'{ctx.command} can not be used in Private Messages.')
except discord.HTTPException:
pass

# For this error example we check to see where it came from...
elif isinstance(error, commands.BadArgument):
if ctx.command.qualified_name == 'tag list': # Check if the command being invoked is 'tag list'
await ctx.send('I could not find that member. Please try again.')

else:
# All other Errors not returned come here. And we can just print the default TraceBack.
print('Ignoring exception in command {}:'.format(ctx.command), file=sys.stderr)
traceback.print_exception(type(error), error, error.__traceback__, file=sys.stderr)


async def setup(client: discord.Client):
await client.add_cog(ErrorHandler(client))
56 changes: 44 additions & 12 deletions cogs/Help.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,56 @@
from modules.data_handler import GuildData


ignore_cogs = ["ErrorHandler", "Help"]

class Help(commands.Cog):

# The first thing that happens when this class is called
def __init__(self, client: discord.Client):
self.client = client

@commands.command(name="Help", description="Returns all the available commmands")
async def help(self, ctx: commands.Context):
prefix = GuildData.get_value(ctx.guild, "prefix")
embed = create_embed(
"Help Command",
f"These are the avaliable commands for **{ctx.guild.name}**\nThe client prefix is: `{prefix}`"
)

for command in self.client.commands:
embed.add_field(name=f"**❯ {command.name}**", value=f"```{command.description}```", inline=True)

await ctx.send(embed=embed)
@commands.command(name="Help", description="Returns all the available commmands", help="This command can be run with a optional parameter (Cog name or Command name)")
async def help(self, ctx: commands.Context, command_name: str = None):
embed_local: discord.Embed;

if command_name is None:
prefix = GuildData.get_value(ctx.guild, "prefix")
embed_local = create_embed(
"Help Command",
f"These are the avaliable modules for **{ctx.guild.name}**\nThe client prefix is: `{prefix}`\n\nTo get help on a specific module, run: `{prefix}help ModuleName`"
)

# Run checks whether user has access to these commands
# Get Command String
command_dict = {} # Dictionary with all cogs and their commands

for cog_name in self.client.cogs:
if cog_name in ignore_cogs:
continue;

# Check if user can run commands in this cog, add those commands to the dictionary
command_dict[cog_name] = [(await x.can_run(ctx) and x.name) for x in self.client.get_cog(cog_name).get_commands()]

if len(command_dict[cog_name]) == 0:
continue;

embed_local.add_field(name=cog_name, value="```" + ("\n".join(command_dict[cog_name])) + "```", inline=True)

else:
# Get Command help
if (command := self.client.get_command(command_name)) is not None:
embed_local = create_embed(command.name, f"**Help**\n```{command.help}```\n**Description**\n```{command.description}```")
else:
# Display All Commands
embed_local = create_embed(
"All Commands",
f"These are the avaliable commands for **{ctx.guild.name}**\nThe client prefix is: `{prefix}`"
)
for command in self.client.commands:
embed_local.add_field(name=f"{command.name}", value=f"```{command.description}```", inline=True)


await ctx.send(embed=embed_local)


async def setup(client: discord.Client):
Expand Down
83 changes: 75 additions & 8 deletions cogs/Moderation.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@ def __init__(self, client: discord.Client):
self.client = client

@commands.command(name="Mute", description="Mute a user")
@commands.guild_only()
@commands.has_permissions(administrator=True)
# member: discord.Member = None basically checks if the member has type discord.Member, if not then let it be None
async def mute(self, ctx: commands.Context, member: discord.Member = None):
# If member has the default value
if member is None:
await ctx.send("User was not passed")
await ctx.send(embed=create_embed("Error", "User was not passed"))
return

role = discord.utils.get(member.guild.roles, name="Muted")
Expand All @@ -33,12 +34,24 @@ async def mute(self, ctx: commands.Context, member: discord.Member = None):
)
)

log_channel_id = GuildData.get_value_default(ctx.guild, "moderation_logs_channel_id", None)
if log_channel_id is None:
if (log_channel := discord.utils.get(ctx.guild.text_channels, name="modlogs")) is None:
return;
else:
log_channel = ctx.guild.get_channel(int(log_channel_id))
if log_channel is None:
return;

await log_channel.send(embed=create_embed("Muted", f"User `{member.display_name}` was muted by `{ctx.message.author.display_name}`"))

@commands.command(name="Unmute", description="Unmute a user")
@commands.guild_only()
@commands.has_permissions(administrator=True)
async def unmute(self, ctx: commands.Context, member: discord.Member = None):
if member is None:
await ctx.send("User was not passed")
return
await ctx.send(embed=create_embed("Error", "User was not passed"))
return;

role = discord.utils.get(member.guild.roles, name="Muted")
await member.remove_roles(role)
Expand All @@ -49,15 +62,29 @@ async def unmute(self, ctx: commands.Context, member: discord.Member = None):
)
)

log_channel_id = GuildData.get_value_default(ctx.guild, "moderation_logs_channel_id", None)
if log_channel_id is None:
if (log_channel := discord.utils.get(ctx.guild.text_channels, name="modlogs")) is None:
return;
else:
log_channel = ctx.guild.get_channel(int(log_channel_id))
if log_channel is None:
return;

await log_channel.send(embed=create_embed("Unmuted", f"User `{member.display_name}` was unmuted by `{ctx.message.author.display_name}`"))



@commands.command(name="Nick", description="Give user nickname", aliases=["nickname", "rename"])
@commands.guild_only()
@commands.has_permissions(administrator=True)
async def nick(self, ctx: commands.Context, member: discord.Member = None,
# Default value for nickname
new_nickname=f"User_{random.randrange(1000, 100000)}"):

if member is None:
await ctx.send("Member / Nickname was not passed")
return
await ctx.send(embed=create_embed("Error", "Member / Nickname was not passed"))
return;

await member.edit(nick=new_nickname)
await ctx.send(
Expand All @@ -67,8 +94,21 @@ async def nick(self, ctx: commands.Context, member: discord.Member = None,
)
)

log_channel_id = GuildData.get_value_default(ctx.guild, "moderation_logs_channel_id", None)
if log_channel_id is None:
if (log_channel := discord.utils.get(ctx.guild.text_channels, name="modlogs")) is None:
return;
else:
log_channel = ctx.guild.get_channel(int(log_channel_id))
if log_channel is None:
return;

await log_channel.send(embed=create_embed("Set Nickname", f"User `{member.display_name}` was nicked by by `{ctx.message.author.display_name}`"))


@commands.command(name="Register", description="Register for using commands that store data", aliases=["verify"])
@commands.guild_only()
@commands.cooldown(1, 1000, commands.BucketType.user)
async def register(self, ctx: commands.Context):
author: discord.Message.author = ctx.message.author # Message Author
user_id: str = str(author.id) # Author ID converted to String
Expand All @@ -78,7 +118,8 @@ async def register(self, ctx: commands.Context):
if (local_data := UserData.get_user_data(user_id)) is not None:
if (name := local_data.get('name', None)) is not None and name != "":
if role in author.roles:
await ctx.send(create_embed("Registered", "You are already registered"))
await ctx.send(embed=create_embed("Registered", "You are already registered"))
ctx.command.reset_cooldown(ctx)
return;

# Send initial message
Expand All @@ -87,22 +128,35 @@ async def register(self, ctx: commands.Context):

# Name Prompt
await bot_message.edit(embed=create_embed("Name", "Enter username, has to be an ascii character [A-Z,a-z] and under 10 characters"))
client_response: discord.Message = await self.client.wait_for('message', check=check(author), timeout=30) # Wait for client input
try:
client_response: discord.Message = await self.client.wait_for('message', check=check(author), timeout=30) # Wait for client input
except asyncio.exceptions.TimeoutError:
await bot_message.edit(embed=create_embed("Timeout", "You took too long to respond. Please run the command again."))
ctx.command.reset_cooldown(ctx)
return;

name: str = client_response.content

# Check if name is valid
if not name.isalpha() or len(name) > 10:
await bot_message.edit(embed=create_embed("Invalid Characters in Name", "Characters must be [A-Z,a-z] (Max 10 characters) \nRun the !register command again."))
ctx.command.reset_cooldown(ctx)
return;


# Terms And Conditions
if (terms := GuildData.get_value_default(ctx.guild, "terms_and_conditions", "")) != "":
await bot_message.edit(embed=create_embed("Terms and Conditions", f"{terms}\n\nDo you agree with these conditions? [Y | N] [Yes | No]"))
client_response = await self.client.wait_for('message', check=check(author), timeout=30)
try:
client_response: discord.Message = await self.client.wait_for('message', check=check(author), timeout=30)
except asyncio.exceptions.TimeoutError:
await bot_message.edit(embed=create_embed("Timeout", "You took too long to respond. Please run the command again."))
ctx.command.reset_cooldown(ctx)
return;

if client_response.content.lower() not in ["y", "yes"]: # Check if user agrees
await bot_message.edit(embed=create_embed("Error", "You did not agree to the Terms of Conditions."))
ctx.command.reset_cooldown(ctx)
return;


Expand All @@ -116,6 +170,19 @@ async def register(self, ctx: commands.Context):
await member.add_roles(role, reason=f"Spyder Verified, {name}")
print(f"Verified {name}")

ctx.command.reset_cooldown(ctx)

log_channel_id = GuildData.get_value_default(ctx.guild, "moderation_logs_channel_id", None)
if log_channel_id is None:
if (log_channel := discord.utils.get(ctx.guild.text_channels, name="modlogs")) is None:
return;
else:
log_channel = ctx.guild.get_channel(int(log_channel_id))
if log_channel is None:
return

await log_channel.send(embed=create_embed("Verified", f"User `{member.display_name}` was Verified"))


async def setup(client: discord.Client):
await client.add_cog(Moderation(client))
2 changes: 1 addition & 1 deletion cogs/Music.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ async def pause(self, ctx):
except (KeyError, AttributeError) as Err:
await ctx.send(embed=create_embed("Error", f"Bot is not currently in a voice channel. Run the `play` command to play music."))

@commands.command(name="resume", description="Resumes the current music.", aliases=["unpause"])
@commands.command(name="Resume", description="Resumes the current music.", aliases=["unpause"])
async def resume(self, ctx):
guild_data = music_guilds[ctx.guild.id]
try:
Expand Down
12 changes: 9 additions & 3 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def get_prefix(client: discord.Client, message: discord.Message):
@client.event
async def on_ready():
print("Bot is online")
await client.change_presence(activity=discord.Activity(type=discord.ActivityType.watching, name=f"{len(client.guilds)} servers")) # Set presence
for guild in client.guilds: # Loop through guilds
GuildData.initialize_guild(guild) # Initialize Temp Guild Data

Expand Down Expand Up @@ -105,7 +106,8 @@ async def on_message_delete(message: discord.Message):
if message.bot == True: return;
log_channel_id = GuildData.get_value_default(guild, "chatlogs_channel_id", None) # Get chatlog ID in database
if log_channel_id is None:
log_channel = discord.utils.get(guild.text_channels, name="chatlogs"); # Try finding a channel called chatlogs
if (log_channel := discord.utils.get(guild.text_channels, name="chatlogs")) is not None: # Try finding a channel called chatlogs
return;
else:
log_channel = guild.get_channel(int(log_channel_id))
if log_channel is None:
Expand All @@ -128,14 +130,17 @@ async def on_message_edit(before: discord.Message, after: discord.Message):
guild = before.guild;
log_channel_id = GuildData.get_value_default(guild, "chatlogs_channel_id", None) # Get chatlog ID in database
if log_channel_id is None:
log_channel = discord.utils.get(guild.text_channels, name="chatlogs"); # Try finding a channel called chatlogs
if (log_channel := discord.utils.get(guild.text_channels, name="chatlogs")) is None: # Try finding a channel called chatlogs
return;
else:
log_channel = guild.get_channel(int(log_channel_id))
if log_channel is None:
return
return;

embed = create_embed("Message Edited", f"User `{before.author.display_name}` edited their message")

if before.content is None and after.content is None: return; # Skip if both values are None

before_message = f"```{before.content if before.content else 'None'}```"
after_message = f"```{after.content if after.content else 'None'}```"

Expand All @@ -150,6 +155,7 @@ async def on_message_edit(before: discord.Message, after: discord.Message):
# Add it to the guilds event
@client.event
async def on_guild_join(ctx: commands.Context):
await client.change_presence(activity=discord.Activity(type=discord.ActivityType.watching, name=f"{len(client.guilds)} servers"))
GuildData.initialize_guild(ctx.guild) # Initialize New Guild Data


Expand Down
1 change: 1 addition & 0 deletions modules/data_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"stats_message_id": 0,
"fact_channel_id": 0,
"chatlogs_channel_id": 0,
"moderation_logs_channel_id": 0,
"prefix": "!",
"terms_and_conditions": "",
}
Expand Down

0 comments on commit f8e96e4

Please sign in to comment.