From 929e8352553bbe90a196548c9afabd0ef63bd98e Mon Sep 17 00:00:00 2001 From: kosayoda Date: Thu, 1 Oct 2020 12:55:00 +0800 Subject: [PATCH 1/3] Fuzzy match roles for `!role` command. An arbitrary cutoff score of 80 is chosen because it works. A bug in the test for the same command is also fixed. --- bot/exts/info/information.py | 10 +++++++--- tests/bot/exts/info/test_information.py | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/bot/exts/info/information.py b/bot/exts/info/information.py index f6ed176f1a..0386a29092 100644 --- a/bot/exts/info/information.py +++ b/bot/exts/info/information.py @@ -6,7 +6,8 @@ from string import Template from typing import Any, Mapping, Optional, Tuple, Union -from discord import ChannelType, Colour, CustomActivity, Embed, Guild, Member, Message, Role, Status, utils +import fuzzywuzzy +from discord import ChannelType, Colour, CustomActivity, Embed, Guild, Member, Message, Role, Status from discord.abc import GuildChannel from discord.ext.commands import BucketType, Cog, Context, Paginator, command, group, has_any_role from discord.utils import escape_markdown @@ -108,18 +109,21 @@ async def role_info(self, ctx: Context, *roles: Union[Role, str]) -> None: parsed_roles = [] failed_roles = [] + all_roles = {role.id: role.name for role in ctx.guild.roles} for role_name in roles: if isinstance(role_name, Role): # Role conversion has already succeeded parsed_roles.append(role_name) continue - role = utils.find(lambda r: r.name.lower() == role_name.lower(), ctx.guild.roles) + match = fuzzywuzzy.process.extractOne(role_name, all_roles, score_cutoff=80) - if not role: + if not match: failed_roles.append(role_name) continue + # `match` is a (role name, score, role id) tuple + role = ctx.guild.get_role(match[2]) parsed_roles.append(role) if failed_roles: diff --git a/tests/bot/exts/info/test_information.py b/tests/bot/exts/info/test_information.py index d3f2995fb4..7bc7dbb5d9 100644 --- a/tests/bot/exts/info/test_information.py +++ b/tests/bot/exts/info/test_information.py @@ -68,7 +68,7 @@ def test_role_info_command(self): permissions=discord.Permissions(0), ) - self.ctx.guild.roles.append([dummy_role, admin_role]) + self.ctx.guild.roles.extend([dummy_role, admin_role]) self.cog.role_info.can_run = unittest.mock.AsyncMock() self.cog.role_info.can_run.return_value = True From 063fb4cbed635e91404e3be833629d5ad1cdf136 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Thu, 1 Oct 2020 13:08:55 +0800 Subject: [PATCH 2/3] Use basic scorer to fuzz a bit stricter. This prevents weird fuzz matches like `!role a b c d` working. --- bot/exts/info/information.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bot/exts/info/information.py b/bot/exts/info/information.py index 0386a29092..bfc05cea93 100644 --- a/bot/exts/info/information.py +++ b/bot/exts/info/information.py @@ -116,7 +116,10 @@ async def role_info(self, ctx: Context, *roles: Union[Role, str]) -> None: parsed_roles.append(role_name) continue - match = fuzzywuzzy.process.extractOne(role_name, all_roles, score_cutoff=80) + match = fuzzywuzzy.process.extractOne( + role_name, all_roles, score_cutoff=80, + scorer=fuzzywuzzy.fuzz.ratio + ) if not match: failed_roles.append(role_name) From c57b9dd7db59ed39ff1fb2fe99ba515bf3e51815 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Thu, 1 Oct 2020 13:11:02 +0800 Subject: [PATCH 3/3] Avoid duplicate roles. --- bot/exts/info/information.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bot/exts/info/information.py b/bot/exts/info/information.py index bfc05cea93..0f074c45d9 100644 --- a/bot/exts/info/information.py +++ b/bot/exts/info/information.py @@ -106,14 +106,14 @@ async def role_info(self, ctx: Context, *roles: Union[Role, str]) -> None: To specify multiple roles just add to the arguments, delimit roles with spaces in them using quotation marks. """ - parsed_roles = [] - failed_roles = [] + parsed_roles = set() + failed_roles = set() all_roles = {role.id: role.name for role in ctx.guild.roles} for role_name in roles: if isinstance(role_name, Role): # Role conversion has already succeeded - parsed_roles.append(role_name) + parsed_roles.add(role_name) continue match = fuzzywuzzy.process.extractOne( @@ -122,12 +122,12 @@ async def role_info(self, ctx: Context, *roles: Union[Role, str]) -> None: ) if not match: - failed_roles.append(role_name) + failed_roles.add(role_name) continue # `match` is a (role name, score, role id) tuple role = ctx.guild.get_role(match[2]) - parsed_roles.append(role) + parsed_roles.add(role) if failed_roles: await ctx.send(f":x: Could not retrieve the following roles: {', '.join(failed_roles)}")