Skip to content

Commit 665cfce

Browse files
authored
Merge pull request #3 from matthewflegg/patch-1
Patched AI chatbot errors and created a generic error handler
2 parents 18fdd56 + 05727f8 commit 665cfce

File tree

7 files changed

+176
-14
lines changed

7 files changed

+176
-14
lines changed

_chatbot/__init__.py

Lines changed: 0 additions & 1 deletion
This file was deleted.

app.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,25 @@
44

55
from discord.ext import commands
66
from dotenv import load_dotenv
7-
87
load_dotenv(".env")
98

109
intents = discord.Intents.all()
1110
intents.members = True
1211

1312
bot = commands.Bot(command_prefix=".", intents=intents)
13+
extensions = ["greetings", "miscellaneous", "admin", "music",
14+
"help", "chatbot", "error_handler"]
1415

15-
extensions = ["greetings", "miscellaneous", "admin", "music", "help"]
16-
_ = [bot.load_extension(f"extensions.{ext}") for ext in extensions]
17-
18-
optional_extensions = ["chatbot"]
19-
_ = [bot.load_extension(f"optional_extensions.{opt_ext}") for opt_ext in optional_extensions]
20-
16+
for ext in extensions:
17+
bot.load_extension(f"extensions.{ext}")
2118

2219
@bot.event
2320
async def on_ready():
2421
"""Displays a series of logs to the screen when the bot has loaded"""
2522

26-
log.info(f"Username: {bot.user.name}")
27-
log.info(f"Bot ID: {bot.user.id}")
28-
log.info(f"Bot loaded successfully")
23+
print(f"Username: {bot.user.name}")
24+
print(f"Bot ID: {bot.user.id}")
25+
print(f"Bot loaded successfully")
2926

3027

3128
TOKEN = os.getenv("TOKEN")

chatbot/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .model import init_chatbot

_chatbot/_model.py renamed to chatbot/model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ def init_chatbot():
1010
chatbot = ChatBot_(
1111
"Beep Boop Bot",
1212
storage_adapter="chatterbot.storage.SQLStorageAdapter",
13-
database_uri="sqlite:///_chatbot_db/db.sqlite3"
13+
database_uri="sqlite:///chatbot\chatbot_db\db.sqlite3"
1414
)
1515

1616
trainer = ChatterBotCorpusTrainer(chatbot)

extensions/admin.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
from discord.ext import commands
44

55

6+
class AdminCommandError(commands.CommandError):
7+
"""Exception class for admin commands"""
8+
9+
610
class Admin(commands.Cog):
711
"""Command category for administrator only commands"""
812
def __init__(self, bot):
@@ -24,10 +28,10 @@ async def ban_(self, ctx: commands.Context, user: discord.Member, *, reason=None
2428

2529
await ctx.send(f"**{user.name}** has successfully been banned")
2630

27-
@commands.command(name="kick", aliases=["k"], description="Kicks a user from a server")
31+
@commands.command(name="kick", aliases=["k"], description="Kicks a user from a server.")
2832
@commands.has_permissions(kick_members=True)
2933
async def kick_(self, ctx: commands.Context, user: discord.Member, *, reason=None):
30-
"""Kicks a user from a server,
34+
"""Kicks a user from a server.
3135
Requires the 'Kick members' permission
3236
"""
3337

extensions/chatbot.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
from datetime import datetime
2+
import discord
3+
import asyncio
4+
import chatbot
5+
from discord.ext import commands
6+
7+
8+
class ChatBot(commands.Cog):
9+
"""Command/event category for the AI chatbot"""
10+
def __init__(self, bot: commands.Bot):
11+
self.chatbot = chatbot.init_chatbot() # Initialise the chatbot model from the _chatbot module
12+
self.bot = bot
13+
self.enabled_channels = [] # A list of all the channels AI chat is enabled in (!!! THIS MIGHT BE DODGY !!!)
14+
self.time_enabled = None
15+
16+
@commands.Cog.listener()
17+
async def on_message(self, message: discord.Message):
18+
"""Gets a response from the AI chatbot when someone sends a message"""
19+
20+
if message.author.bot or message.channel not in self.enabled_channels:
21+
return # Do nothing if the bot sent the message or if this isn't an enabled channel
22+
if message.created_at < self.time_enabled: # Ignore all messages before the chatbot was enabled
23+
return
24+
if message.content.startswith("."): # If the message is a command, ignore it
25+
return
26+
27+
# Show 'bot is typing' for 2 seconds
28+
ctx = await self.bot.get_context(message)
29+
30+
async with ctx.typing():
31+
await asyncio.sleep(2)
32+
33+
# Get and send an AI response
34+
response_ = self.chatbot.get_response(message.content)
35+
await message.channel.send(f"{message.author.mention}: {response_}")
36+
37+
@commands.command(name="aienable", aliases=["ai", "enableaichat"],
38+
description="Enables AI chatbot responses to all messages within a channel."
39+
+ "Requires 'Manage channels' permissions")
40+
@commands.has_permissions(manage_channels=True)
41+
async def enable_ai_chat(self, ctx: commands.Context):
42+
"""Enables AI chatbot responses in a text channel"""
43+
44+
# Add the channel to the list. Check in on_message if this is an enabled channel
45+
if ctx.channel not in self.enabled_channels: # Check if not already enabled
46+
self.enabled_channels.append(ctx.channel)
47+
self.time_enabled = datetime.now()
48+
49+
await ctx.send("✅ Done! I will now use AI to respond to all messages in this channel\n" +
50+
"You can use .aidisable or .disableaichat to disable chatbot responses")
51+
else:
52+
await ctx.send("❌ You've already enabled AI chat in this channel.")
53+
54+
@enable_ai_chat.error
55+
async def enable_ai_chat_error(self, ctx: commands.Context, error):
56+
"""Called when the enable AI chat command throws an error"""
57+
58+
if isinstance(error, commands.MissingPermissions):
59+
await ctx.send("❌ You don't have permission to enable AI chat in this channel")
60+
61+
@commands.command(name="aidisable", aliases=["disableaichat"],
62+
description="Disables AI chatbot responses to all messages within a channel."
63+
+ "Requires 'Manage channels' permissions")
64+
@commands.has_permissions(manage_channels=True)
65+
async def disable_ai_chat(self, ctx: commands.Context):
66+
"""Disables AI chatbot responses in a text channel"""
67+
68+
if ctx.channel in self.enabled_channels: # If the channel is enabled, disable it
69+
self.enabled_channels.remove(ctx.channel)
70+
71+
await ctx.send("✅ Done! I will no longer use AI to respond to all messages in this channel\n" +
72+
"You can use .aienable, .ai, or .enableaichat to enable chatbot responses")
73+
else:
74+
await ctx.send("❌ You've already disabled AI chat in this channel.")
75+
76+
@disable_ai_chat.error
77+
async def disable_ai_chat_error(self, ctx: commands.Context, error):
78+
"""Called when the disable AI chat command throws an error"""
79+
80+
if isinstance(error, commands.MissingPermissions):
81+
await ctx.send("❌ You don't have permission to disable AI chat in this channel")
82+
83+
84+
def setup(bot: commands.Bot):
85+
"""Adds the 'ChatBot' cog to the bot"""
86+
bot.add_cog(ChatBot(bot))

extensions/error_handler.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import traceback
2+
import discord
3+
import math
4+
import sys
5+
from discord.ext import commands
6+
7+
8+
class GenericErrorHandler(commands.Cog):
9+
"""Generic error handler.
10+
Used if local (cog-specific) error handlers fail to catch exceptions.
11+
"""
12+
def __init__(self, bot: commands.Bot):
13+
self.bot = bot
14+
15+
@commands.Cog.listener()
16+
async def on_command_error(self, ctx: commands.Context, error):
17+
"""Generic command error handler."""
18+
if hasattr(ctx.command, "on_error"): # Don't override local errors
19+
return
20+
21+
error = getattr(error, "original", error)
22+
23+
if isinstance(error, commands.CommandNotFound):
24+
return
25+
26+
if isinstance(error, commands.BotMissingPermissions): # Error handling for missing bot perms
27+
missing = [perm.replace("_", " ").replace("guild", "server").title() for perm in error.missing_perms]
28+
29+
if len(missing) > 2:
30+
format_ = f"{'**, **'.join(missing[: -1])}, and {missing[-1]}"
31+
else:
32+
format_ = " and ".join(missing)
33+
34+
message_ = f"I need the **{format_}** permission(s) to use this command."
35+
await ctx.send(message_)
36+
return
37+
38+
if isinstance(error, commands.DisabledCommand): # Command is disabled
39+
await ctx.send("This command has been disabled.")
40+
return
41+
42+
if isinstance(error, commands.CommandOnCooldown): # Command is currently on cooldown
43+
await ctx.send(f"This command is currently on cooldown. Try again in {math.ceil(error.retry_after)}s")
44+
return
45+
46+
if isinstance(error, commands.MissingPermissions): # Error handling for missing user perms
47+
missing = [perm.replace("_", " ").replace("guild", "server").title() for perm in error.missing_perms]
48+
49+
if len(missing) > 2:
50+
format_ = f"{'**, **'.join(missing[: -1])}, and {missing[-1]}"
51+
else:
52+
format_ = " and ".join(missing)
53+
54+
message_ = f"You need the **{format_}** permission(s) to use this command."
55+
await ctx.send(message_)
56+
return
57+
58+
if isinstance(error, commands.UserInputError): # If the bot receives bad input
59+
await ctx.send("Sorry, that input is invalid.")
60+
return
61+
62+
if isinstance(error, commands.NoPrivateMessage): # If the user can't use the command in a DM
63+
try:
64+
await ctx.author.send("You can't use this command in private messages.")
65+
except discord.Forbidden:
66+
pass
67+
return
68+
69+
print(f"Ignoring exceptions in command {ctx.command}", file=sys.stderr) # Print errors to stderr
70+
traceback.print_exception(type(error), error, error.__traceback__, file=sys.stderr)
71+
72+
73+
def setup(bot: commands.Bot):
74+
"""Adds the 'GenericErrorHandler' cog to the bot."""
75+
bot.add_cog(GenericErrorHandler(bot))

0 commit comments

Comments
 (0)