fix: F3 guard per_page>=1; swallow on_timeout HTTPException

per_page<=0 raised ZeroDivisionError (==0) or yielded a negative max_pages (<0) at the
divmod; now a clear ValueError. on_timeout swallows a transient delete HTTPException (with
a log) so a best-effort cleanup doesn't surface as an unretrieved-task traceback. add the
module logger.

Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
disqualifier 2026-06-29 21:35:01 -04:00
parent 1416375e40
commit fa5cdf3e1d

View File

@ -29,6 +29,7 @@ config-free: no host config import; everything is passed at construction.
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
import logging
from typing import ( from typing import (
Any, Any,
Dict, Dict,
@ -69,6 +70,8 @@ DEFAULT_EMOJIS = {
"cache": "\U0001f5c2\ufe0f", # 🗂️ "cache": "\U0001f5c2\ufe0f", # 🗂️
} }
log = logging.getLogger(__name__)
PageT_co = TypeVar("PageT_co", bound=Page, covariant=True) PageT_co = TypeVar("PageT_co", bound=Page, covariant=True)
@ -161,6 +164,10 @@ class ButtonPaginator(Generic[PageT_co], discord.ui.View):
super().__init__(timeout=timeout) super().__init__(timeout=timeout)
if not pages: if not pages:
raise ValueError("ButtonPaginator requires at least one page") raise ValueError("ButtonPaginator requires at least one page")
if per_page < 1:
# per_page <= 0 would ZeroDivisionError (==0) or yield a negative max_pages
# (<0) at the divmod below; fail loud like the other construction guards
raise ValueError("per_page must be >= 1")
self.author_id: Optional[int] = author_id self.author_id: Optional[int] = author_id
self.delete_message_after: bool = delete_message_after self.delete_message_after: bool = delete_message_after
self.mentions_allowed = mentions_allowed or discord.AllowedMentions.all() self.mentions_allowed = mentions_allowed or discord.AllowedMentions.all()
@ -427,3 +434,7 @@ class ButtonPaginator(Generic[PageT_co], discord.ui.View):
await self.message.delete() await self.message.delete()
except (discord.NotFound, discord.Forbidden): except (discord.NotFound, discord.Forbidden):
pass pass
except discord.HTTPException:
# on_timeout runs as a fire-and-forget task; a transient delete failure must
# not surface as an unretrieved-task traceback on a best-effort cleanup
log.warning("paginator on_timeout: failed to delete message", exc_info=True)