diff --git a/README.md b/README.md
index 5d1168c88..1b769c540 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ TrustyJAID's Cogs for [Red-DiscordBot](https://github.com/Cog-Creators/Red-Disc
| Reddit | 1.2.0 | A cog to post updates from reddit.
Reddit commands for getting updates on specified subreddits. | TrustyJAID |
| Rekt | 1.0.0 | Get REKT
Are you REKT? | TrustyJAID |
| ReTrigger | 2.29.1 | Trigger events via Regular Expressions!
Trigger events based on regex! Check out and for help setting up the cog. Note: This cog can become quite resource heavy. Optional features are available if the requirements are present such as pillow for image resizing and pytesseract to scan images for text (OCR). | TrustyJAID |
-| RoleTools | 1.5.14 | Various role related tools.
Various role utility commands. Including Reaction roles, Sticky roles, and Auto role. | TrustyJAID |
+| RoleTools | 1.5.15 | Various role related tools.
Various role utility commands. Including Reaction roles, Sticky roles, and Auto role. | TrustyJAID |
| runescape | 1.5.2 | Show your Runescape stats in discord!
A cog to grab Runescape and OSRS stats and profile information. | TrustyJAID |
| ServerStats | 1.8.0 | A plethora of potentially useful commands for any bot owner.
A plethora of potentially useful commands for any bot owner. Includes a way to track the bot joining new servers, find cheaters on global economies, get user avatars and even larger emojis. | TrustyJAID and Preda |
| Spotify | 1.7.3 | Control Spotify through Discord!
This cog allows you to control Spotify via OAuth through the bot on discord. Use `[p]spotify` to see available commands. | TrustyJAID and NeuroAssassin |
diff --git a/roletools/buttons.py b/roletools/buttons.py
index 6390475ea..54d5ee1c3 100644
--- a/roletools/buttons.py
+++ b/roletools/buttons.py
@@ -5,6 +5,7 @@
from redbot.core import commands
from redbot.core.commands import Context
from redbot.core.i18n import Translator
+from redbot.core.utils.chat_formatting import pagify
from .abc import RoleToolsMixin
from .components import ButtonRole, RoleToolsView
@@ -152,6 +153,7 @@ async def create_button(
role_id=role.id,
name=name.lower(),
)
+ failed_fixes = []
for message_id in button_settings["messages"]:
# fix old buttons with the new one when interacted with
replacement_view = self.views.get(ctx.guild.id, {}).get(message_id, None)
@@ -160,11 +162,30 @@ async def create_button(
for item in replacement_view.children:
if item.custom_id == custom_id:
replacement_view.remove_item(item)
- replacement_view.add_item(button)
+ try:
+ replacement_view.add_item(button)
+ except ValueError:
+ failed_fixes.append(message_id)
button.replace_label(ctx.guild)
view = RoleToolsView(self, timeout=180.0)
view.add_item(button)
await ctx.send("Here is how your button will look.", view=view)
+ if failed_fixes:
+ msg = ""
+ for view_id in failed_fixes:
+ channel_id, message_id = view_id.split("-")
+ channel = ctx.guild.get_channel(int(channel_id))
+ if channel is None:
+ continue
+ message = discord.PartialMessage(channel=channel, id=int(message_id))
+ msg += f"- {message.jump_url}\n"
+ pages = []
+ full_msg = _(
+ "The following existing buttons could not be edited with the new settings.\n{failed}"
+ ).format(failed=msg)
+ for page in pagify(full_msg):
+ pages.append(page)
+ await ctx.send_interactive(pages)
await self.confirm_selfassignable(ctx, [role])
@buttons.command(name="delete", aliases=["del", "remove"])
diff --git a/roletools/components.py b/roletools/components.py
index ed1afc8ee..f9d25f96d 100644
--- a/roletools/components.py
+++ b/roletools/components.py
@@ -245,6 +245,7 @@ async def callback(self, interaction: discord.Interaction):
missing_role = False
pending = False
wait = None
+ config = self.view.cog.config
for role_id in role_ids:
role = guild.get_role(role_id)
if role is None:
@@ -255,7 +256,7 @@ async def callback(self, interaction: discord.Interaction):
# # even if it's your own code
# ## Especially if it's your own code
continue
- config = self.view.cog.config
+
if role not in interaction.user.roles:
if not await config.role(role).selfassignable():
msg += _(
@@ -278,6 +279,10 @@ async def callback(self, interaction: discord.Interaction):
interaction.user, [role], _("Role Selection")
)
if response:
+ reason = response[0].reason
+ msg += _("The {role} role could not be given because: {reason}").format(
+ role=role.mention, reason=reason
+ )
continue
added_roles.append(role)
elif role in interaction.user.roles:
@@ -287,7 +292,15 @@ async def callback(self, interaction: discord.Interaction):
).format(role=role.mention)
continue
# log.debug("Removing role from %s in %s", interaction.user.name, guild)
- await self.view.cog.remove_roles(interaction.user, [role], _("Role Selection"))
+ response = await self.view.cog.remove_roles(
+ interaction.user, [role], _("Role Selection")
+ )
+ if response:
+ reason = response[0].reason
+ msg += _("The {role} role could not be removed because: {reason}").format(
+ role=role.mention, reason=reason
+ )
+ continue
removed_roles.append(role)
if wait is not None:
msg += _(
diff --git a/roletools/messages.py b/roletools/messages.py
index 16f4437e6..7eb3e9652 100644
--- a/roletools/messages.py
+++ b/roletools/messages.py
@@ -1,6 +1,6 @@
from __future__ import annotations
-from typing import List, Optional, Set
+from typing import List, Optional
import discord
from red_commons.logging import getLogger
@@ -82,7 +82,28 @@ async def send_message(
for button in buttons:
new_view.add_item(button)
content = text[:2000] if text else None
- msg = await channel.send(content=content, view=new_view)
+ try:
+ msg = await channel.send(content=content, view=new_view)
+ except discord.HTTPException as e:
+ log.exception(
+ "Error sending message in channel %s in guild %s",
+ channel.id,
+ channel.guild.id,
+ )
+ await ctx.send(
+ _("There was an error sending to {channel}. Reason: {reason}").format(
+ channel=channel.mention, reason=e.text
+ )
+ )
+ # Right now this just displays discord's error message but could probably
+ # be improved to parse the error and display to the user which component
+ # is actually erroring. This requires accessing private attributes
+ # in d.py though so I will skip it for now. Namely only item._rendered_row
+ # contains the actual row with reference to the item we could convert to components
+ # but I don't think I can accurately match that back to the actual object.
+ # TODO: Parse discord's error with the following regex
+ # In (?Pcomponents\.(?P\d)\.components\.(?P\d)\.(?P\w+))
+ return
message_key = f"{msg.channel.id}-{msg.id}"
await self.save_settings(
@@ -172,11 +193,31 @@ async def edit_message(
new_view.add_item(select_menu)
for button in buttons:
new_view.add_item(button)
- await message.edit(view=new_view)
+ failed_to_edit = None
+ try:
+ await message.edit(view=new_view)
+ except discord.HTTPException as e:
+ failed_to_edit = e.text
+ log.exception(
+ "Error editing message %s in channel %s in guild %s",
+ message.id,
+ message.channel.id,
+ message.guild.id,
+ )
message_key = f"{message.channel.id}-{message.id}"
await self.check_and_replace_existing(ctx.guild.id, message_key)
await self.save_settings(ctx.guild, message_key, buttons=buttons, select_menus=menus)
self.views[ctx.guild.id][message_key] = new_view
+ if failed_to_edit:
+ await ctx.send(
+ _(
+ "There was an error editing the message. "
+ "It's possible the emojis were deleted or there was some "
+ "misconfiguration with the view. You may need to rebuild things "
+ "from scratch with the changes. Reason: {reason}"
+ ).format(reason=failed_to_edit)
+ )
+ return
await ctx.send(_("Message edited."))
@roletools_message.command(
@@ -221,7 +262,20 @@ async def send_select(
for select in menus:
new_view.add_item(select)
content = text[:2000] if text else None
- msg = await channel.send(content=content, view=new_view)
+ try:
+ msg = await channel.send(content=content, view=new_view)
+ except discord.HTTPException as e:
+ log.exception(
+ "Error sending message in channel %s in guild %s",
+ channel.id,
+ channel.guild.id,
+ )
+ await ctx.send(
+ _("There was an error sending to {channel}. Reason: {reason}").format(
+ channel=channel.mention, reason=e.text
+ )
+ )
+ return
message_key = f"{msg.channel.id}-{msg.id}"
await self.save_settings(ctx.guild, message_key, buttons=[], select_menus=menus)
@@ -259,12 +313,32 @@ async def edit_with_select(
new_view = RoleToolsView(self)
for select_menu in menus:
new_view.add_item(select_menu)
- await message.edit(view=new_view)
+ failed_to_edit = None
+ try:
+ await message.edit(view=new_view)
+ except discord.HTTPException as e:
+ failed_to_edit = e.text
+ log.exception(
+ "Error editing message %s in channel %s in guild %s",
+ message.id,
+ message.channel.id,
+ message.guild.id,
+ )
message_key = f"{message.channel.id}-{message.id}"
await self.check_and_replace_existing(ctx.guild.id, message_key)
await self.save_settings(ctx.guild, message_key, buttons=[], select_menus=menus)
self.views[ctx.guild.id][message_key] = new_view
+ if failed_to_edit:
+ await ctx.send(
+ _(
+ "There was an error editing the message. "
+ "It's possible the emojis were deleted or there was some "
+ "misconfiguration with the view. You may need to rebuild things "
+ "from scratch with the changes. Reason: {reason}"
+ ).format(reason=failed_to_edit)
+ )
+ return
await ctx.send(_("Message edited."))
@roletools_message.command(
@@ -303,7 +377,20 @@ async def send_buttons(
for button in buttons:
new_view.add_item(button)
content = text[:2000] if text else None
- msg = await channel.send(content=content, view=new_view)
+ try:
+ msg = await channel.send(content=content, view=new_view)
+ except discord.HTTPException as e:
+ log.exception(
+ "Error sending message in channel %s in guild %s",
+ channel.id,
+ channel.guild.id,
+ )
+ await ctx.send(
+ _("There was an error sending to {channel}. Reason: {reason}").format(
+ channel=channel.mention, reason=e.text
+ )
+ )
+ return
message_key = f"{msg.channel.id}-{msg.id}"
await self.save_settings(ctx.guild, message_key, buttons=buttons, select_menus=[])
@@ -337,10 +424,30 @@ async def edit_with_buttons(
new_view = RoleToolsView(self)
for button in buttons:
new_view.add_item(button)
- await message.edit(view=new_view)
+ failed_to_edit = None
+ try:
+ await message.edit(view=new_view)
+ except discord.HTTPException as e:
+ failed_to_edit = e.text
+ log.exception(
+ "Error editing message %s in channel %s in guild %s",
+ message.id,
+ message.channel.id,
+ message.guild.id,
+ )
message_key = f"{message.channel.id}-{message.id}"
await self.check_and_replace_existing(ctx.guild.id, message_key)
await self.save_settings(ctx.guild, message_key, buttons=buttons, select_menus=[])
self.views[ctx.guild.id][message_key] = new_view
+ if failed_to_edit:
+ await ctx.send(
+ _(
+ "There was an error editing the message. "
+ "It's possible the emojis were deleted or there was some "
+ "misconfiguration with the view. You may need to rebuild things "
+ "from scratch with the changes. Reason: {reason}"
+ ).format(reason=failed_to_edit)
+ )
+ return
await ctx.send(_("Message edited."))
diff --git a/roletools/roletools.py b/roletools/roletools.py
index 48c8c0346..29cbf385d 100644
--- a/roletools/roletools.py
+++ b/roletools/roletools.py
@@ -85,7 +85,7 @@ class RoleTools(
"""
__author__ = ["TrustyJAID"]
- __version__ = "1.5.14"
+ __version__ = "1.5.15"
def __init__(self, bot: Red):
self.bot = bot
diff --git a/roletools/select.py b/roletools/select.py
index ff5c5976a..0888e7734 100644
--- a/roletools/select.py
+++ b/roletools/select.py
@@ -5,7 +5,7 @@
from redbot.core import commands
from redbot.core.commands import Context
from redbot.core.i18n import Translator
-from redbot.core.utils.chat_formatting import humanize_list
+from redbot.core.utils.chat_formatting import humanize_list, pagify
from .abc import RoleToolsMixin
from .components import RoleToolsView, SelectRole, SelectRoleOption
@@ -144,18 +144,16 @@ async def create_select_menu(
msg = _("The name should be less than 70 characters long.")
await ctx.send(msg)
return
- min_values = extras.min_values
- max_values = extras.max_values
- if min_values is None:
- min_values = 1
- if max_values is None:
- max_values = len(options)
+ min_values = max(min(25, getattr(extras, "min_values", 1) or 1), 0)
+ max_values = max(
+ min(len(options), getattr(extras, "max_values", len(options)) or len(options)), 1
+ )
messages = []
custom_id = f"RTSelect-{name.lower()}-{ctx.guild.id}"
select_menu_settings = {
"options": [o.name for o in options],
- "min_values": max(min(25, min_values), 0),
- "max_values": max(min(25, max_values), 0),
+ "min_values": min_values,
+ "max_values": max_values,
"placeholder": extras.placeholder,
"name": name.lower(),
"messages": messages,
@@ -176,6 +174,7 @@ async def create_select_menu(
options=options,
placeholder=extras.placeholder,
)
+ failed_fixes = []
for message_id in select_menu_settings["messages"]:
# fix old menus with the new one when interacted with
replacement_view = self.views.get(ctx.guild.id, {}).get(message_id, None)
@@ -188,14 +187,30 @@ async def create_select_menu(
replacement_view.add_item(select_menus)
except ValueError:
log.error("Error editing old menu on Select Menu %s", custom_id)
+ failed_fixes.append(message_id)
continue
-
select_menus.update_options(ctx.guild)
view = RoleToolsView(self, timeout=180.0)
view.add_item(select_menus)
msg_str = _("Here is how your select menu will look.")
- msg = await ctx.send(msg_str, view=view)
+ await ctx.send(msg_str, view=view)
+ if failed_fixes:
+ msg = ""
+ for view_id in failed_fixes:
+ channel_id, message_id = view_id.split("-")
+ channel = ctx.guild.get_channel(int(channel_id))
+ if channel is None:
+ continue
+ message = discord.PartialMessage(channel=channel, id=int(message_id))
+ msg += f"- {message.jump_url}\n"
+ pages = []
+ full_msg = _(
+ "The following existing select menus could not be edited with the new settings.\n{failed}"
+ ).format(failed=msg)
+ for page in pagify(full_msg):
+ pages.append(page)
+ await ctx.send_interactive(pages)
@select.command(name="delete", aliases=["del", "remove"])
async def delete_select_menu(self, ctx: Context, *, name: str) -> None: