272 lines
7.6 KiB
Python
272 lines
7.6 KiB
Python
from datetime import timedelta
|
|
|
|
import re
|
|
|
|
import discord
|
|
|
|
from payphone.db import models
|
|
from payphone.core import constants
|
|
|
|
|
|
def trim(text: str, length: int = 100) -> str:
|
|
"""Trims a string if it's too long.
|
|
|
|
Args:
|
|
text (str): The original text.
|
|
length (int, optional): The max length of the string before trimming. Defaults to 100.
|
|
|
|
Returns:
|
|
str: The trimmed (or not) text.
|
|
"""
|
|
|
|
if len(text) > length:
|
|
return text[:length - 3] + "..."
|
|
|
|
return text
|
|
|
|
|
|
def text_join(texts: list[str], join_word: str = "and") -> str:
|
|
"""Joins a list with commas and the last item with an "and".
|
|
|
|
Args:
|
|
texts (list[str]): The list of texts to concatenate.
|
|
join_word (str): The word to use at the end of the joint.
|
|
|
|
Returns:
|
|
str: The formatted concatenation.
|
|
"""
|
|
|
|
if len(texts) > 1:
|
|
return f"{", ".join(texts[:-1])} {join_word} {texts[-1]}"
|
|
|
|
return texts[0]
|
|
|
|
|
|
def monospace_grid(values: list[str], space: int = 14) -> str:
|
|
"""Creates a monospace grid.
|
|
|
|
Args:
|
|
values (list[str]): All the values that the list must contain
|
|
space (int, optional): The width of each column. Defaults to 14.
|
|
|
|
Returns:
|
|
str: The formatted grid.
|
|
"""
|
|
|
|
return "```" + "".join([v + " " * (space - len(v)) for v in values]) + "```"
|
|
|
|
|
|
def monospace_table(
|
|
rows: list[list[str | tuple[str, str]]],
|
|
headers: list | None = None,
|
|
column_separator: str = "|",
|
|
header_separator: str = "-",
|
|
intersection: str = "+",
|
|
padding: int = 1,
|
|
) -> str:
|
|
"""Creates a monospace table.
|
|
|
|
Creates a monospace table. Row columns can be either a string with the column's content, or a
|
|
tuple with two items, being the first one aligned to the left and the second one aligned to the
|
|
right. Non-string values will be converted to string using `str(value)`.
|
|
|
|
Args:
|
|
rows (list[list[str | tuple[str, str]]]): The rows.
|
|
headers (list | None, optional): The headers.
|
|
column_separator (str, optional): The column separator. Defaults to "|".
|
|
header_separator (str, optional): The header separator. Defaults to "-".
|
|
intersection (str, optional): The intersection character where column and row separators
|
|
meet.
|
|
padding (int, optional): The padding that each column has at its sides.
|
|
|
|
Returns:
|
|
str: The monospace table.
|
|
"""
|
|
|
|
max_sizes = (
|
|
[
|
|
max(
|
|
*(
|
|
[
|
|
(
|
|
len(str(row[i]))
|
|
if not isinstance(row[i], tuple)
|
|
else (sum(len(str(item)) for item in row[i]) + 1)
|
|
)
|
|
for row in rows
|
|
]
|
|
+ ([len(headers[i])] if headers is not None else [])
|
|
)
|
|
)
|
|
for i in range(len(rows[0]))
|
|
]
|
|
if len(rows) >= 2
|
|
else (
|
|
[
|
|
(
|
|
len(str(col))
|
|
if not isinstance(col, tuple)
|
|
else (sum(len(str(item)) for item in col) + 1)
|
|
)
|
|
for col in rows[0]
|
|
]
|
|
if len(rows) > 0
|
|
else []
|
|
)
|
|
)
|
|
|
|
def format_row(row: list) -> str:
|
|
return column_separator.join(
|
|
[
|
|
(" " * padding)
|
|
+ (
|
|
(str(col) + (" " * (max_sizes[i] - len(str(col)))))
|
|
if not isinstance(col, tuple)
|
|
else (
|
|
str(col[0])
|
|
+ (" " * (max_sizes[i] - len(str(col[0])) - len(str(col[1]))))
|
|
+ str(col[1])
|
|
)
|
|
)
|
|
+ (" " * padding)
|
|
for i, col in enumerate(row)
|
|
]
|
|
)
|
|
|
|
formatted_rows = []
|
|
|
|
for row in rows:
|
|
formatted_rows.append(format_row(row))
|
|
|
|
table = "\n".join(formatted_rows)
|
|
|
|
if headers is not None:
|
|
header = format_row(headers)
|
|
|
|
separator = intersection.join(
|
|
[header_separator * (size + padding * 2) for size in max_sizes]
|
|
)
|
|
|
|
table = f"{header}\n{separator}\n{table}"
|
|
|
|
return table
|
|
|
|
|
|
def clean(
|
|
message: discord.Message, call_rss: list[models.CallUserChannelRelationship]
|
|
) -> str:
|
|
"""Cleans the message.
|
|
|
|
Args:
|
|
message (discord.Message): The message to clean.
|
|
call_rss (list[models.CallUserChannelRelationship]): The call's relationships. They must
|
|
have pre-selected their `profile` relation.
|
|
|
|
Returns:
|
|
str: The cleaned message content.
|
|
"""
|
|
|
|
cleaned_message = message.content
|
|
|
|
if message.mentions:
|
|
for call_rs in call_rss:
|
|
cleaned_message = cleaned_message.replace(
|
|
f"<@{call_rs.user_id}>",
|
|
(
|
|
f"@**{call_rs.display_name}**"
|
|
if message.channel.id == call_rs.channel_id
|
|
else f"@**{call_rs.profile.name}**"
|
|
),
|
|
)
|
|
|
|
for user in message.mentions:
|
|
cleaned_message = cleaned_message.replace(user.mention, "@*Not in call*")
|
|
|
|
for channel in message.channel_mentions:
|
|
cleaned_message = cleaned_message.replace(
|
|
f"<#{channel.id}>", f"#**{channel.name}**"
|
|
)
|
|
|
|
for role in message.role_mentions:
|
|
cleaned_message = cleaned_message.replace(f"<@&{role.id}>", f"@**{role.name}**")
|
|
|
|
cleaned_message = cleaned_message.replace(
|
|
"@everyone", f"@{constants.INVISIBLE_CHARACTER}everyone"
|
|
).replace("@here", f"@{constants.INVISIBLE_CHARACTER}here")
|
|
|
|
return cleaned_message
|
|
|
|
|
|
def time_to_text(time_diff: timedelta) -> str:
|
|
"""Converts a timedelta to its string representation.
|
|
|
|
Args:
|
|
time_diff (timedelta): The timedelta.
|
|
|
|
Returns:
|
|
str: The string representation.
|
|
"""
|
|
|
|
years = time_diff.days // 365
|
|
days = time_diff.days % 365
|
|
hours, remainder = divmod(time_diff.seconds, 3600)
|
|
minutes, seconds = divmod(remainder, 60)
|
|
|
|
parts = []
|
|
if years > 0:
|
|
parts.append(f"{years} year{"s" if years != 1 else ""}")
|
|
if days > 0:
|
|
parts.append(f"{days} day{"s" if days != 1 else ""}")
|
|
if hours > 0:
|
|
parts.append(f"{hours} hour{"s" if hours != 1 else ""}")
|
|
if minutes > 0:
|
|
parts.append(f"{minutes} min{"s" if minutes != 1 else ""}")
|
|
if seconds > 0:
|
|
parts.append(f"{seconds} sec{"s" if seconds != 1 else ""}")
|
|
|
|
return " ".join(parts)
|
|
|
|
|
|
def parse_timedelta(text: str) -> timedelta:
|
|
"""Parses a timedelta string formatted as 30h 45m to a timedelta object.
|
|
|
|
Args:
|
|
text (str): The formatted string.
|
|
|
|
Raises:
|
|
ValueError: The string is invalid.
|
|
|
|
Returns:
|
|
datetime.timedelta: The timedelta object.
|
|
"""
|
|
|
|
pattern = re.compile(
|
|
r"(?:(\d+)y)?\s*" # years
|
|
r"(?:(\d+)mo)?\s*" # months
|
|
r"(?:(\d+)w)?\s*" # weeks
|
|
r"(?:(\d+)d)?\s*" # days
|
|
r"(?:(\d+)h)?\s*" # hours
|
|
r"(?:(\d+)m)?\s*" # minutes
|
|
r"(?:(\d+)s)?" # seconds
|
|
)
|
|
matches = pattern.match(text.strip())
|
|
|
|
if not matches:
|
|
raise ValueError("Invalid time string format")
|
|
|
|
years, months, weeks, days, hours, minutes, seconds = matches.groups()
|
|
|
|
years = int(years) if years else 0
|
|
months = int(months) if months else 0
|
|
weeks = int(weeks) if weeks else 0
|
|
days = int(days) if days else 0
|
|
hours = int(hours) if hours else 0
|
|
minutes = int(minutes) if minutes else 0
|
|
seconds = int(seconds) if seconds else 0
|
|
|
|
days += weeks * 7
|
|
days += months * 30
|
|
days += years * 365
|
|
|
|
return timedelta(days=days, hours=hours, minutes=minutes, seconds=seconds)
|