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 REKTAre 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: