rewrite: New lib utils.
New `MessageArgs` util. Rewrite `prop_tabulate` into `tabulate`. New `EmbedField` util. New `parse_ids` util. New `DotDict` util class.
This commit is contained in:
228
bot/utils/lib.py
228
bot/utils/lib.py
@@ -1,9 +1,14 @@
|
|||||||
|
from typing import NamedTuple, Optional, Sequence, Union, overload, List
|
||||||
import datetime
|
import datetime
|
||||||
import iso8601 # type: ignore
|
import iso8601 # type: ignore
|
||||||
import re
|
import re
|
||||||
from contextvars import Context
|
from contextvars import Context
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
from discord import Embed, File, GuildSticker, StickerItem, AllowedMentions, Message, MessageReference, PartialMessage
|
||||||
|
from discord.ui import View
|
||||||
|
|
||||||
|
from meta.errors import UserInputError
|
||||||
|
|
||||||
# from cmdClient.lib import SafeCancellation
|
# from cmdClient.lib import SafeCancellation
|
||||||
|
|
||||||
@@ -16,32 +21,178 @@ tick = '✅'
|
|||||||
cross = '❌'
|
cross = '❌'
|
||||||
|
|
||||||
|
|
||||||
def prop_tabulate(prop_list: list[str], value_list: list[str], indent=True, colon=True) -> str:
|
class MessageArgs:
|
||||||
"""
|
"""
|
||||||
Turns a list of properties and corresponding list of values into
|
Utility class for storing message creation and editing arguments.
|
||||||
|
"""
|
||||||
|
# TODO: Overrides for mutually exclusive arguments, see Messageable.send
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
content: Optional[str] = ...,
|
||||||
|
*,
|
||||||
|
tts: bool = ...,
|
||||||
|
embed: Embed = ...,
|
||||||
|
file: File = ...,
|
||||||
|
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
|
||||||
|
delete_after: float = ...,
|
||||||
|
nonce: Union[str, int] = ...,
|
||||||
|
allowed_mentions: AllowedMentions = ...,
|
||||||
|
reference: Union[Message, MessageReference, PartialMessage] = ...,
|
||||||
|
mention_author: bool = ...,
|
||||||
|
view: View = ...,
|
||||||
|
suppress_embeds: bool = ...,
|
||||||
|
) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
content: Optional[str] = ...,
|
||||||
|
*,
|
||||||
|
tts: bool = ...,
|
||||||
|
embed: Embed = ...,
|
||||||
|
files: Sequence[File] = ...,
|
||||||
|
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
|
||||||
|
delete_after: float = ...,
|
||||||
|
nonce: Union[str, int] = ...,
|
||||||
|
allowed_mentions: AllowedMentions = ...,
|
||||||
|
reference: Union[Message, MessageReference, PartialMessage] = ...,
|
||||||
|
mention_author: bool = ...,
|
||||||
|
view: View = ...,
|
||||||
|
suppress_embeds: bool = ...,
|
||||||
|
) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
content: Optional[str] = ...,
|
||||||
|
*,
|
||||||
|
tts: bool = ...,
|
||||||
|
embeds: Sequence[Embed] = ...,
|
||||||
|
file: File = ...,
|
||||||
|
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
|
||||||
|
delete_after: float = ...,
|
||||||
|
nonce: Union[str, int] = ...,
|
||||||
|
allowed_mentions: AllowedMentions = ...,
|
||||||
|
reference: Union[Message, MessageReference, PartialMessage] = ...,
|
||||||
|
mention_author: bool = ...,
|
||||||
|
view: View = ...,
|
||||||
|
suppress_embeds: bool = ...,
|
||||||
|
) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
content: Optional[str] = ...,
|
||||||
|
*,
|
||||||
|
tts: bool = ...,
|
||||||
|
embeds: Sequence[Embed] = ...,
|
||||||
|
files: Sequence[File] = ...,
|
||||||
|
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
|
||||||
|
delete_after: float = ...,
|
||||||
|
nonce: Union[str, int] = ...,
|
||||||
|
allowed_mentions: AllowedMentions = ...,
|
||||||
|
reference: Union[Message, MessageReference, PartialMessage] = ...,
|
||||||
|
mention_author: bool = ...,
|
||||||
|
view: View = ...,
|
||||||
|
suppress_embeds: bool = ...,
|
||||||
|
) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.kwargs = kwargs
|
||||||
|
|
||||||
|
@property
|
||||||
|
def send_args(self) -> dict:
|
||||||
|
return self.kwargs
|
||||||
|
|
||||||
|
@property
|
||||||
|
def edit_args(self) -> dict:
|
||||||
|
args = {}
|
||||||
|
kept = (
|
||||||
|
'content', 'embed', 'embeds', 'delete_after', 'allowed_mentions', 'view'
|
||||||
|
)
|
||||||
|
for k in kept:
|
||||||
|
if k in self.kwargs:
|
||||||
|
args[k] = self.kwargs[k]
|
||||||
|
|
||||||
|
if 'file' in self.kwargs:
|
||||||
|
args['attachments'] = [self.kwargs['file']]
|
||||||
|
|
||||||
|
if 'files' in self.kwargs:
|
||||||
|
args['attachments'] = self.kwargs['files']
|
||||||
|
|
||||||
|
if 'suppress_embeds' in self.kwargs:
|
||||||
|
args['suppress'] = self.kwargs['suppress_embeds']
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def tabulate(
|
||||||
|
*fields: tuple[str, str],
|
||||||
|
row_format: str = "`{invis}{key:<{pad}}{colon}`\t{value}",
|
||||||
|
sub_format: str = "`{invis:<{pad}}{invis}`\t{value}",
|
||||||
|
colon: str = ':',
|
||||||
|
invis: str = "",
|
||||||
|
**args
|
||||||
|
) -> list[str]:
|
||||||
|
"""
|
||||||
|
Turns a list of (property, value) pairs into
|
||||||
a pretty string with one `prop: value` pair each line,
|
a pretty string with one `prop: value` pair each line,
|
||||||
padded so that the colons in each line are lined up.
|
padded so that the colons in each line are lined up.
|
||||||
Handles empty props by using an extra couple of spaces instead of a `:`.
|
Use `\\r\\n` in a value to break the line with padding.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
prop_list: List[str]
|
fields: List[tuple[str, str]]
|
||||||
List of short names to put on the right side of the list.
|
List of (key, value) pairs.
|
||||||
Empty props are considered to be "newlines" for the corresponding value.
|
row_format: str
|
||||||
value_list: List[str]
|
The format string used to format each row.
|
||||||
List of values corresponding to the properties above.
|
sub_format: str
|
||||||
indent: bool
|
The format string used to format each subline in a row.
|
||||||
Whether to add padding so the properties are right-adjusted.
|
colon: str
|
||||||
|
The colon character used.
|
||||||
|
invis: str
|
||||||
|
The invisible character used (to avoid Discord stripping the string).
|
||||||
|
|
||||||
Returns: str
|
Returns: List[str]
|
||||||
|
The list of resulting table rows.
|
||||||
|
Each row corresponds to one (key, value) pair from fields.
|
||||||
"""
|
"""
|
||||||
max_len = max(len(prop) for prop in prop_list)
|
max_len = max(len(field[0]) for field in fields)
|
||||||
return "".join(["`{}{}{}`\t{}{}".format(" " * (max_len - len(prop)) if indent else "",
|
|
||||||
prop,
|
rows = []
|
||||||
(":" if len(prop) else " " * 2) if colon else '',
|
for field in fields:
|
||||||
value_list[i],
|
key = field[0]
|
||||||
'' if str(value_list[i]).endswith("```") else '\n')
|
value = field[1]
|
||||||
for i, prop in enumerate(prop_list)])
|
lines = value.split('\r\n')
|
||||||
|
|
||||||
|
row_line = row_format.format(
|
||||||
|
invis=invis,
|
||||||
|
key=key,
|
||||||
|
pad=max_len,
|
||||||
|
colon=colon,
|
||||||
|
value=lines[0],
|
||||||
|
field=field,
|
||||||
|
**args
|
||||||
|
)
|
||||||
|
if len(lines) > 1:
|
||||||
|
row_lines = [row_line]
|
||||||
|
for line in lines[1:]:
|
||||||
|
sub_line = sub_format.format(
|
||||||
|
invis=invis,
|
||||||
|
pad=max_len + len(colon),
|
||||||
|
value=line,
|
||||||
|
**args
|
||||||
|
)
|
||||||
|
row_lines.append(sub_line)
|
||||||
|
row_line = '\n'.join(row_lines)
|
||||||
|
rows.append(row_line)
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
def paginate_list(item_list: list[str], block_length=20, style="markdown", title=None) -> list[str]:
|
def paginate_list(item_list: list[str], block_length=20, style="markdown", title=None) -> list[str]:
|
||||||
@@ -382,6 +533,12 @@ async def mail(client: discord.Client, channelid: int, **msg_args) -> discord.Me
|
|||||||
return await channel.send(**msg_args)
|
return await channel.send(**msg_args)
|
||||||
|
|
||||||
|
|
||||||
|
class EmbedField(NamedTuple):
|
||||||
|
name: str
|
||||||
|
value: str
|
||||||
|
inline: Optional[bool] = True
|
||||||
|
|
||||||
|
|
||||||
def emb_add_fields(embed: discord.Embed, emb_fields: list[tuple[str, str, bool]]):
|
def emb_add_fields(embed: discord.Embed, emb_fields: list[tuple[str, str, bool]]):
|
||||||
"""
|
"""
|
||||||
Append embed fields to an embed.
|
Append embed fields to an embed.
|
||||||
@@ -461,3 +618,38 @@ def multiple_replace(string: str, rep_dict: dict[str, str]) -> str:
|
|||||||
def recover_context(context: Context):
|
def recover_context(context: Context):
|
||||||
for var in context:
|
for var in context:
|
||||||
var.set(context[var])
|
var.set(context[var])
|
||||||
|
|
||||||
|
|
||||||
|
def parse_ids(idstr: str) -> List[int]:
|
||||||
|
"""
|
||||||
|
Parse a provided comma separated string of maybe-mentions, maybe-ids, into a list of integer ids.
|
||||||
|
|
||||||
|
Object agnostic, so all mention tokens are stripped.
|
||||||
|
Raises UserInputError if an id is invalid,
|
||||||
|
setting `orig` and `item` info fields.
|
||||||
|
"""
|
||||||
|
# Extract ids from string
|
||||||
|
splititer = (split.strip('<@!#&>, ') for split in idstr.split(','))
|
||||||
|
splits = [split for split in splititer if split]
|
||||||
|
|
||||||
|
# Check they are integers
|
||||||
|
if (not_id := next((split for split in splits if not split.isdigit()), None)) is not None:
|
||||||
|
raise UserInputError("Could not extract an id from `$item`!", {'orig': idstr, 'item': not_id})
|
||||||
|
|
||||||
|
# Cast to integer and return
|
||||||
|
return list(map(int, splits))
|
||||||
|
|
||||||
|
|
||||||
|
def error_embed(error, **kwargs) -> discord.Embed:
|
||||||
|
embed = discord.Embed(
|
||||||
|
colour=discord.Colour.red(),
|
||||||
|
description=error,
|
||||||
|
timestamp=utc_now()
|
||||||
|
)
|
||||||
|
return embed
|
||||||
|
|
||||||
|
|
||||||
|
class DotDict(dict):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.__dict__ = self
|
||||||
|
|||||||
Reference in New Issue
Block a user