Repository: python-discord/bot

Issue:
Enhance off-topic name search feature
We've recently added a search feature for off-topic names. It's a really cool feature, but I think it can be enhanced in a couple of ways. (Use the informal definition of "couple": ["an indefinite small number."](https://www.merriam-webster.com/dictionary/couple))

For all the examples, this is the list of off-topic-names in the database:

![2019-09-17_18-07](https://user-images.githubusercontent.com/33516116/65059284-07b23a80-d976-11e9-9df9-3d5630004202.png)

**1. Use `OffTopicName` converter for the `query` argument**
We don't use the characters as-is in off-topic names, but we use a translation table on the string input. However, we don't do that yet for the search query of the search feature. This leads to interesting search results:

![2019-09-17_18-07_1](https://user-images.githubusercontent.com/33516116/65059311-1567c000-d976-11e9-8081-a0218ed7dc00.png)

If we change the annotation to `query: OffTopicName`, we utilize the same conversion for the query as we use for the actual off-topic-names, leading to much better results:

![2019-09-17_18-03](https://user-images.githubusercontent.com/33516116/65058948-6e832400-d975-11e9-9eaf-3ef9d56712ba.png)

**2. Add a simple membership test of `query in offtopicname`**
Currently, we utilize `difflib.get_close_match` to get results that closely match the query. However, this function will match based on the whole off-topic name, so it doesn't do well if we search for a substring of the off-topic name. (I suspect this is part of the reason we use relatively low cutoff.)

While there's clearly a "pool" in volcyy's underground, we don't find it:

![2019-09-17_18-11](https://user-images.githubusercontent.com/33516116/65059630-a2127e00-d976-11e9-8766-e14fb2142d05.png)

If we add a simple membership test to the existing `get_close_match`, we do get the results we expect:

![2019-09-17_18-13](https://user-images.githubusercontent.com/33516116/65059723-cd956880-d976-11e9-9c99-8fa03b1fd6eb.png)

**3. Given the membership test of suggestion 2, up the cutoff**
The low cutoff does allow us some leeway when searching for partial off-topic names, but it also causes interesting results. Using the current search feature, without the membership testing suggested in 2, we don't find "eevee-is-cooler-than-python" when we search for "eevee", but "eivl's-evil-eval":
![2019-09-17_18-14](https://user-images.githubusercontent.com/33516116/65059873-0d5c5000-d977-11e9-8097-0e4ff750356c.png)

However, if we up the cutoff value and combine it with an additional membership test, we can search for "sounds-like" off-topic names and find off-topic names that contain exact matches:

![2019-09-17_18-19](https://user-images.githubusercontent.com/33516116/65060191-b60aaf80-d977-11e9-9bf1-8c8e0bb443a8.png)

![2019-09-17_18-19_1](https://user-images.githubusercontent.com/33516116/65060199-ba36cd00-d977-11e9-9c14-3645a3dc9f6e.png)


Discussion:

Resolving patch:
diff --git a/bot/cogs/off_topic_names.py b/bot/cogs/off_topic_names.py
--- a/bot/cogs/off_topic_names.py
+++ b/bot/cogs/off_topic_names.py
@@ -144,20 +144,21 @@ async def list_command(self, ctx):
 
     @otname_group.command(name='search', aliases=('s',))
     @with_role(*MODERATION_ROLES)
-    async def search_command(self, ctx, *, query: str):
+    async def search_command(self, ctx, *, query: OffTopicName):
         """
         Search for an off-topic name.
         """
 
         result = await self.bot.api_client.get('bot/off-topic-channel-names')
-        matches = difflib.get_close_matches(query, result, n=10, cutoff=0.35)
-        lines = sorted(f"• {name}" for name in matches)
+        in_matches = {name for name in result if query in name}
+        close_matches = difflib.get_close_matches(query, result, n=10, cutoff=0.70)
+        lines = sorted(f"• {name}" for name in in_matches.union(close_matches))
         embed = Embed(
             title=f"Query results",
             colour=Colour.blue()
         )
 
-        if matches:
+        if lines:
             await LinePaginator.paginate(lines, ctx, embed, max_size=400, empty=False)
         else:
             embed.description = "Nothing found."


Test patch: