Merge branch 'rewrite' into pillow

This commit is contained in:
2023-09-24 10:58:59 +03:00
178 changed files with 29691 additions and 2267 deletions

View File

@@ -25,6 +25,8 @@ multiselect_regex = re.compile(
tick = ''
cross = ''
MISSING = object()
class MessageArgs:
"""
@@ -113,7 +115,13 @@ class MessageArgs:
@property
def send_args(self) -> dict:
return self.kwargs
if self.kwargs.get('view', MISSING) is None:
kwargs = self.kwargs.copy()
kwargs.pop('view')
else:
kwargs = self.kwargs
return kwargs
@property
def edit_args(self) -> dict:
@@ -804,3 +812,20 @@ def recurse_map(func, obj, loc=[]):
else:
obj = func(loc, obj)
return obj
async def check_dm(user: discord.User | discord.Member) -> bool:
"""
Check whether we can direct message the given user.
Assumes the client is initialised.
This uses an always-failing HTTP request,
so we need to be very very very careful that this is not used frequently.
Optimally only at the explicit behest of the user
(i.e. during a user instigated interaction).
"""
try:
await user.send('')
except discord.Forbidden:
return False
except discord.HTTPException:
return True

View File

@@ -67,9 +67,9 @@ class Bucket:
def request(self):
self._leak()
if self._level + 1 > self.max_level + 1:
if self._level > self.max_level:
raise BucketOverFull
elif self._level + 1 > self.max_level:
elif self._level == self.max_level:
self._level += 1
if self._last_full:
raise BucketOverFull

View File

@@ -40,7 +40,10 @@ class DurationTransformer(Transformer):
duration = parse_duration(value)
if duration is None:
raise UserInputError(
t(_p('utils:parse_dur|error', "Cannot parse `{value}` as a duration.")).format(
t(_p(
'utils:parse_dur|error',
"Cannot parse `{value}` as a duration."
)).format(
value=value
)
)

View File

@@ -10,6 +10,8 @@ from discord.ui import Modal, View, Item
from meta.logger import log_action_stack, logging_context
from meta.errors import SafeCancellation
from gui.errors import RenderingException
from . import logger
from ..lib import MessageArgs, error_embed
@@ -48,6 +50,16 @@ class LeoUI(View):
# TODO: Replace this with a substitutable ViewLayout class
self._layout: Optional[tuple[tuple[Item, ...], ...]] = None
@property
def _stopped(self) -> asyncio.Future:
"""
Return an future indicating whether the View has finished interacting.
Currently exposes a hidden attribute of the underlying View.
May be reimplemented in future.
"""
return self._View__stopped
def to_components(self) -> List[Dict[str, Any]]:
"""
Extending component generator to apply the set _layout, if it exists.
@@ -218,17 +230,29 @@ class LeoUI(View):
f"Caught a safe cancellation from LeoUI: {e.details}",
extra={'action': 'Cancel'}
)
except RenderingException as e:
logger.info(
f"UI interaction failed due to rendering exception: {repr(e)}"
)
embed = interaction.client.tree.rendersplat(e)
await interaction.client.tree.error_reply(interaction, embed)
except Exception:
logger.exception(
f"Unhandled interaction exception occurred in item {item!r} of LeoUI {self!r}",
f"Unhandled interaction exception occurred in item {item!r} of LeoUI {self!r} from interaction: "
f"{interaction.data}",
extra={'with_ctx': True, 'action': 'UIError'}
)
# Explicitly handle the bugsplat ourselves
splat = interaction.client.tree.bugsplat(interaction, error)
await interaction.client.tree.error_reply(interaction, splat)
class MessageUI(LeoUI):
"""
Simple single-message LeoUI, intended as a framework for UIs
attached to a single interaction response.
UIs may also be sent as regular messages by using `send(channel)` instead of `run(interaction)`.
"""
def __init__(self, *args, callerid: Optional[int] = None, **kwargs):
@@ -396,8 +420,11 @@ class MessageUI(LeoUI):
try:
await self._redraw(args)
except discord.HTTPException:
# Unknown communication erorr, nothing we can reliably do. Exit quietly.
except discord.HTTPException as e:
# Unknown communication error, nothing we can reliably do. Exit quietly.
logger.warning(
f"Unexpected UI redraw failure occurred in {self}: {repr(e)}",
)
await self.close()
async def cleanup(self):
@@ -449,11 +476,20 @@ class LeoModal(Modal):
"""
try:
raise error
except RenderingException as e:
logger.info(
f"Modal submit failed due to rendering exception: {repr(e)}"
)
embed = interaction.client.tree.rendersplat(e)
await interaction.client.tree.error_reply(interaction, embed)
except Exception:
logger.exception(
f"Unhandled interaction exception occurred in {self!r}",
f"Unhandled interaction exception occurred in {self!r}. Interaction: {interaction.data}",
extra={'with_ctx': True, 'action': 'ModalError'}
)
# Explicitly handle the bugsplat ourselves
splat = interaction.client.tree.bugsplat(interaction, error)
await interaction.client.tree.error_reply(interaction, splat)
def error_handler_for(exc):