Skip to content

Commit 0d6cb37

Browse files
committed
xkcd number autocomplete
1 parent 22e4a9b commit 0d6cb37

1 file changed

Lines changed: 55 additions & 0 deletions

File tree

monty/exts/utils/xkcd.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import random
2+
from html.parser import HTMLParser
23
from typing import Any, TypedDict
34

45
import disnake
56
from disnake.ext import commands, tasks
7+
from rapidfuzz import process
68
from typing_extensions import NotRequired
79

810
from monty.bot import Monty
@@ -29,6 +31,40 @@ class XkcdDict(TypedDict):
2931
safe_title: str
3032
extra_parts: NotRequired[dict[str, Any]]
3133

34+
# I'm sure there is a better way to do any of this, but this worked
35+
class ComicParser(HTMLParser):
36+
def __init__(self):
37+
super().__init__()
38+
self.comic = False
39+
self.list_of_comics = False
40+
self.comics: dict[str, str] = {}
41+
self.number = None
42+
43+
def handle_starttag(self, tag, attrs):
44+
if tag == "a" and self.list_of_comics:
45+
self.comic = True
46+
self.number = None
47+
for attr in attrs:
48+
if attr[0] == "href":
49+
self.number = attr[1].strip("/")
50+
if (
51+
tag == "div"
52+
and len(attrs) > 0
53+
and attrs[0][0] == "id"
54+
and attrs[0][1] == "middleContainer"
55+
):
56+
self.list_of_comics = True
57+
58+
def handle_data(self, data):
59+
if not self.comic:
60+
return
61+
self.comics[data.strip()] = self.number
62+
63+
def handle_endtag(self, tag):
64+
if self.comic and tag == "a":
65+
self.comic = False
66+
if self.list_of_comics and tag == "div":
67+
self.list_of_comics = False
3268

3369
class XKCD(
3470
commands.Cog,
@@ -42,6 +78,7 @@ class XKCD(
4278
def __init__(self, bot: Monty) -> None:
4379
self.bot = bot
4480
self.latest_comic_info: XkcdDict | None = None
81+
self.comics: dict[str, str] | None = None
4582
self.get_latest_comic_info.start()
4683

4784
def cog_unload(self) -> None:
@@ -56,6 +93,13 @@ async def get_latest_comic_info(self) -> None:
5693
self.latest_comic_info = await resp.json()
5794
else:
5895
log.debug(f"Failed to get latest XKCD comic information. Status code {resp.status}")
96+
async with self.bot.http_session.get(f"{BASE_URL}/archive") as resp:
97+
if resp.status == 200:
98+
parser = ComicParser()
99+
parser.feed(resp.text) # parse /archive for all comic titles and comic number
100+
self.comics = parser.comics
101+
else:
102+
log.debug(f"Failed to get latest list of XKCD comics. Status code {resp.status}")
59103

60104
@commands.slash_command(name="xkcd")
61105
async def xkcd(self, _: disnake.ApplicationCommandInteraction) -> None:
@@ -142,6 +186,17 @@ async def number(self, inter: disnake.ApplicationCommandInteraction, comic: int)
142186

143187
await self.send_xkcd(inter, info)
144188

189+
@number.autocomplete("comic")
190+
async def number_autocomplete(self, _: disnake.CommandInteraction, query: str) -> list[disnake.OptionChoice]:
191+
"""Autocomplete names of XKCD comics when searching for number."""
192+
searches = process.extract(query, self.comics.keys(), limit=5)
193+
return [
194+
# Probably shouldn't be returning value as a str, but I am.
195+
disnake.OptionChoice(name=comic, value=self.comics[comic])
196+
for comic
197+
in [res[0] for res in searches]
198+
]
199+
145200
@xkcd.sub_command()
146201
async def random(self, inter: disnake.ApplicationCommandInteraction) -> None:
147202
"""View a random xkcd comic."""

0 commit comments

Comments
 (0)