Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 42 additions & 16 deletions bot/exts/info/pep.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from datetime import UTC, datetime, timedelta
from typing import TypedDict

from discord import Colour, Embed
from discord import Colour, Embed, Interaction, app_commands
from discord.ext.commands import Cog, Context, command
from rapidfuzz import process

from bot.bot import Bot
from bot.log import get_logger
Expand All @@ -13,6 +14,7 @@
ICON_URL = "https://www.python.org/static/opengraph-icon-200x200.png"
PEP_API_URL = "https://peps.python.org/api/peps.json"


class PEPInfo(TypedDict):
"""
Useful subset of the PEP API response.
Expand All @@ -35,6 +37,7 @@ class PythonEnhancementProposals(Cog):
def __init__(self, bot: Bot):
self.bot = bot
self.peps: dict[int, PEPInfo] = {}
self.pep_autocomplete_choices: dict[int, str] = {}
self.last_refreshed_peps: datetime | None = None

async def refresh_pep_data(self) -> None:
Expand All @@ -45,14 +48,14 @@ async def refresh_pep_data(self) -> None:
log.trace("Started refreshing PEP data.")
async with self.bot.http_session.get(PEP_API_URL) as resp:
if resp.status != 200:
log.warning(
"Fetching PEP data from PEP API failed with code %s",
resp.status)
log.warning("Fetching PEP data from PEP API failed with code %s", resp.status)
return
listing = await resp.json()

for pep_num, pep_info in listing.items():
self.peps[int(pep_num)] = pep_info
for pep_num_str, pep_info in listing.items():
pep_num = int(pep_num_str)
self.peps[pep_num] = pep_info
self.pep_autocomplete_choices[pep_num] = pep_info["title"]

log.info("Successfully refreshed PEP data.")

Expand All @@ -72,18 +75,17 @@ def generate_pep_embed(self, pep: PEPInfo) -> Embed:

return embed

@command(name="pep", aliases=("get_pep", "p"))
async def pep_command(self, ctx: Context, pep_number: int) -> None:
"""Fetches information about a PEP and sends it to the channel."""
# Refresh the PEP data up to every hour, as e.g. the PEP status might have changed.
if (
self.last_refreshed_peps is None or (
(self.last_refreshed_peps + timedelta(hours=1)) <= datetime.now(tz=UTC)
and len(str(pep_number)) < 5
)
):
async def refresh_pep_data_if_needed(self, *, pep_number: int | None = None) -> None:
"""Refreshes the PEP data only when a certain criteria is met."""
if self.last_refreshed_peps is None or (self.last_refreshed_peps + timedelta(hours=1)) <= datetime.now(tz=UTC):
if pep_number is not None and len(str(pep_number)) >= 5:
return
await self.refresh_pep_data()

async def get_pep_embed(self, pep_number: int) -> Embed:
"""Refreshes the PEP data if needed and generates the PEP embed."""
await self.refresh_pep_data_if_needed(pep_number=pep_number)

if pep := self.peps.get(pep_number):
embed = self.generate_pep_embed(pep)
else:
Expand All @@ -93,9 +95,33 @@ async def pep_command(self, ctx: Context, pep_number: int) -> None:
description=f"PEP {pep_number} does not exist.",
colour=Colour.red(),
)
return embed

@command(name="pep", aliases=("get_pep", "p"))
async def pep_command(self, ctx: Context, pep_number: int) -> None:
"""Fetches information about a PEP and sends it to the channel."""
# Refresh the PEP data up to every hour, as e.g. the PEP status might have changed.
embed = await self.get_pep_embed(pep_number)
await send_or_reply(ctx, embed)

@app_commands.command(name="pep")
@app_commands.guild_only()
@app_commands.describe(pep_number="The pep number or the autocompleted pep")
async def pep_slash_command(self, interaction: Interaction, pep_number: int) -> None:
"""Fetches information about a PEP and sends it to the channel."""
embed = await self.get_pep_embed(pep_number)
await interaction.response.send_message(embed=embed)

@pep_slash_command.autocomplete("pep_number")
async def pep_slash_command_autocomplete(self, interaction: Interaction, query: str) -> list[app_commands.Choice]:
"""Returns a list of PEPs that matches `query`."""
await self.refresh_pep_data_if_needed()

# list[('pep_title', similarity, pep_number)]
result = process.extract(query=query, choices=self.pep_autocomplete_choices, limit=10)
return [app_commands.Choice(name=f"{pep[2]} - {pep[0]}", value=pep[2]) for pep in result]


async def setup(bot: Bot) -> None:
"""Load the PEP cog."""
await bot.add_cog(PythonEnhancementProposals(bot))