add package: pyproject + src (parse/fit/sanitize/chunk/table/timestamp/confirm/choose/safe_send)

Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
2026-07-02 23:12:53 -04:00
parent 464a1171c3
commit 056516dee9
8 changed files with 736 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "dpy_commons"
version = "0.1.0"
description = "Shared discord.py utilities — message/embed parsing, limit-fitting, link extraction, chunking, timestamps, await-prompts, limit-safe send. Config-free, installable."
requires-python = ">=3.10"
dependencies = [
"discord.py>=2.2",
]
[tool.hatch.build.targets.wheel]
packages = ["src/dpy_commons"]
+61
View File
@@ -0,0 +1,61 @@
"""dpy_commons — shared discord.py utilities (the discord-side sibling of commons)
a module of functions grouped by concern: message/embed parsing, embed sanitizing +
limit-fitting, link extraction, text chunking, timestamp helpers, interactive await-prompts,
and a limit-safe send. see each submodule's docstring for the contract.
"""
from .embeds import fit_embed, sanitize_embed, split_embeds
from .limits import (
EMBED_AUTHOR,
EMBED_DESC,
EMBED_FIELD_NAME,
EMBED_FIELD_VALUE,
EMBED_FOOTER,
EMBED_MAX_FIELDS,
EMBED_TITLE,
EMBED_TOTAL,
MSG_LIMIT,
MSG_MAX_EMBEDS,
)
from .parsing import extract_message_links, parse_message, sanitize_mentions
from .prompts import choose, confirm
from .send import safe_send
from .text import (
chunk_text,
discord_timestamp,
format_table,
humanize_delta,
truncate,
wrap_bare_links,
)
__version__ = "0.1.0"
__all__ = [
"parse_message",
"extract_message_links",
"sanitize_mentions",
"fit_embed",
"sanitize_embed",
"split_embeds",
"chunk_text",
"format_table",
"discord_timestamp",
"humanize_delta",
"truncate",
"wrap_bare_links",
"confirm",
"choose",
"safe_send",
"MSG_LIMIT",
"EMBED_TITLE",
"EMBED_DESC",
"EMBED_FIELD_NAME",
"EMBED_FIELD_VALUE",
"EMBED_FOOTER",
"EMBED_AUTHOR",
"EMBED_TOTAL",
"EMBED_MAX_FIELDS",
"MSG_MAX_EMBEDS",
"__version__",
]
+135
View File
@@ -0,0 +1,135 @@
"""embed helpers: fit an embed within discord's hard limits, sanitize one to be safe-to-send,
and split a list into sendable groups; none of these mutate their input"""
from __future__ import annotations
import logging
import discord
from .limits import (
EMBED_AUTHOR,
EMBED_DESC,
EMBED_FIELD_NAME,
EMBED_FIELD_VALUE,
EMBED_FOOTER,
EMBED_MAX_FIELDS,
EMBED_TITLE,
EMBED_TOTAL,
MSG_MAX_EMBEDS,
)
from .text import truncate, wrap_bare_links
log = logging.getLogger(__name__)
_ZWSP = ""
def _copy(embed: discord.Embed) -> discord.Embed:
"""deep copy via dict round-trip so the input is never mutated"""
return discord.Embed.from_dict(embed.to_dict())
def fit_embed(embed: discord.Embed) -> discord.Embed:
"""return a copy guaranteed within discord's hard limits: title/description/field/footer/
author truncated to their caps, fields beyond 25 dropped, and the description then fields
shrunk until the 6000 total fits; appends an ellipsis where it cuts, never mutates input"""
out = _copy(embed)
if out.title:
out.title = truncate(out.title, EMBED_TITLE)
if out.description:
out.description = truncate(out.description, EMBED_DESC)
footer = out.footer
if footer and footer.text:
out.set_footer(text=truncate(footer.text, EMBED_FOOTER), icon_url=footer.icon_url)
author = out.author
if author and author.name:
out.set_author(name=truncate(author.name, EMBED_AUTHOR), url=author.url, icon_url=author.icon_url)
fields = list(out.fields)
out.clear_fields()
for field in fields[:EMBED_MAX_FIELDS]:
out.add_field(
name=truncate(field.name or _ZWSP, EMBED_FIELD_NAME),
value=truncate(field.value or _ZWSP, EMBED_FIELD_VALUE),
inline=field.inline,
)
_shrink_to_total(out)
return out
def _shrink_to_total(embed: discord.Embed) -> None:
"""truncate the description, then trailing fields, until len(embed) <= EMBED_TOTAL"""
if len(embed) <= EMBED_TOTAL:
return
if embed.description:
overflow = len(embed) - EMBED_TOTAL
keep = max(0, len(embed.description) - overflow)
embed.description = truncate(embed.description, keep) if keep else _ZWSP
if len(embed) <= EMBED_TOTAL:
return
while len(embed) > EMBED_TOTAL and embed.fields:
embed.remove_field(len(embed.fields) - 1)
def sanitize_embed(embed: discord.Embed) -> discord.Embed:
"""return a copy safe to send: non-discord links wrapped in <>, color normalized, empty
strings discord rejects filled with a zero-width space, all other parts preserved, then
run through fit_embed so the result is both clean and within limits; never mutates input"""
out = _copy(embed)
if out.title:
out.title = wrap_bare_links(out.title)
out.description = wrap_bare_links(out.description) if out.description else out.description
out.colour = _normalize_colour(out.colour)
footer = out.footer
if footer and footer.text:
out.set_footer(text=wrap_bare_links(footer.text), icon_url=footer.icon_url)
author = out.author
if author and author.name:
out.set_author(name=wrap_bare_links(author.name), url=author.url, icon_url=author.icon_url)
fields = list(out.fields)
out.clear_fields()
for field in fields:
out.add_field(
name=wrap_bare_links(field.name) if field.name else _ZWSP,
value=wrap_bare_links(field.value) if field.value else _ZWSP,
inline=field.inline,
)
if not out.description and not out.fields:
out.description = _ZWSP
return fit_embed(out)
def _normalize_colour(value: object) -> "discord.Colour | None":
"""coerce a color-ish value to a discord.Colour: Colour passthrough, int, str '#hex',
or an object with a .value; falls back to default() on bad input rather than raising"""
if value is None:
return None
if isinstance(value, discord.Colour):
return value
try:
if isinstance(value, int):
return discord.Colour(value)
if isinstance(value, str):
return discord.Colour(int(value.lstrip("#"), 16))
inner = getattr(value, "value", None)
if isinstance(inner, int):
return discord.Colour(inner)
except (ValueError, TypeError):
log.warning("dpy_commons: could not normalize embed colour %r, using default", value)
return discord.Colour.default()
def split_embeds(embeds: "list[discord.Embed]") -> "list[list[discord.Embed]]":
"""chunk a list of embeds into groups of <= 10 (MSG_MAX_EMBEDS) so each group sends in one
message"""
return [embeds[i:i + MSG_MAX_EMBEDS] for i in range(0, len(embeds), MSG_MAX_EMBEDS)]
+13
View File
@@ -0,0 +1,13 @@
"""discord hard limits — single source of truth; every module references these, nothing
hardcodes a limit inline"""
MSG_LIMIT = 2000
EMBED_TITLE = 256
EMBED_DESC = 4096
EMBED_FIELD_NAME = 256
EMBED_FIELD_VALUE = 1024
EMBED_FOOTER = 2048
EMBED_AUTHOR = 256
EMBED_TOTAL = 6000
EMBED_MAX_FIELDS = 25
MSG_MAX_EMBEDS = 10
+115
View File
@@ -0,0 +1,115 @@
"""parsing / extraction: turn a discord.Message into a structured payload, pull jump-links
out of text, and neutralize raw mention tokens; all pure — no bot/global reached for"""
from __future__ import annotations
import logging
import re
from typing import Union
import discord
from .embeds import fit_embed
from .text import wrap_bare_links
log = logging.getLogger(__name__)
_MESSAGE_LINK_RE = re.compile(
r"https?://(?:ptb\.|canary\.)?discord(?:app)?\.com/channels/(\d+)/(\d+)/(\d+)"
)
_RAW_MENTION_RE = re.compile(r"<@[!&]?\d+>")
def sanitize_mentions(text: str) -> str:
"""neutralize pings in raw text: @everyone/@here -> (at)everyone/(at)here, and raw
<@id>/<@&id> mention tokens -> (at)mention; pure text, resolves no names"""
if not text:
return text
text = text.replace("@everyone", "(at)everyone").replace("@here", "(at)here")
return _RAW_MENTION_RE.sub("(at)mention", text)
def extract_message_links(
text_or_message: "Union[str, discord.Message]",
) -> "list[tuple[int, int, int]]":
"""pull discord message jump-links out of text or a Message's content, returning
(guild_id, channel_id, message_id) tuples"""
text = text_or_message.content if isinstance(text_or_message, discord.Message) else text_or_message
return [(int(g), int(c), int(m)) for g, c, m in _MESSAGE_LINK_RE.findall(text or "")]
async def parse_message(message: discord.Message, *, strict: bool = False) -> dict:
"""extract a discord.Message into a structured payload; pure, reaches for no bot/global
async because attachments are read from discord's cdn (network i/o) — the spec wrote this
sync, but reading attachment bytes cannot be synchronous, so it is a coroutine (await it).
the only tolerated swallow is per-attachment: a read failure warns and skips by default so
one bad attachment doesn't kill the parse; pass strict=True to raise on any such failure
instead"""
return {
"content": wrap_bare_links(sanitize_mentions(message.content or "")),
"mentions": _parse_mentions(message),
"embeds": [fit_embed(embed) for embed in message.embeds],
"attachments": await _parse_attachments(message, strict=strict),
"stickers": [sticker.name for sticker in message.stickers],
"poll": _parse_poll(message),
"components": _parse_components(message),
"reference": message.reference.message_id if message.reference else None,
}
def _parse_mentions(message: discord.Message) -> dict:
"""collect user/role mentions plus everyone/here flags"""
content = message.content or ""
return {
"users": [user.id for user in message.mentions],
"roles": [role.id for role in message.role_mentions],
"everyone": bool(message.mention_everyone) or "@everyone" in content,
"here": "@here" in content,
}
async def _parse_attachments(message: discord.Message, *, strict: bool) -> "list[discord.File]":
"""read each attachment into a discord.File; a read failure warns+skips unless strict"""
files: "list[discord.File]" = []
for attachment in message.attachments:
try:
files.append(await attachment.to_file())
except (discord.HTTPException, discord.NotFound, OSError) as exc:
if strict:
raise
log.warning("dpy_commons: skipping attachment %s: %s", attachment.filename, exc)
return files
def _parse_poll(message: discord.Message) -> "dict | None":
"""extract a poll's question + option texts, or None if the message has no poll"""
poll = getattr(message, "poll", None)
if poll is None:
return None
return {
"question": _poll_text(poll.question),
"options": [_poll_text(answer) for answer in poll.answers],
}
def _poll_text(value: object) -> str:
"""poll question/answer text across discord.py shapes (str, PollMedia, PollAnswer)"""
if isinstance(value, str):
return value
media = getattr(value, "media", None)
if media is not None and getattr(media, "text", None) is not None:
return media.text
return str(value)
def _parse_components(message: discord.Message) -> "list[dict]":
"""flatten action-row buttons to {label, url} entries"""
buttons: "list[dict]" = []
for row in message.components:
for child in getattr(row, "children", []):
label = getattr(child, "label", None)
url = getattr(child, "url", None)
if label is not None or url is not None:
buttons.append({"label": label, "url": url})
return buttons
+218
View File
@@ -0,0 +1,218 @@
"""interactive await-prompts: throw a prompt anywhere, await it, get the chosen VALUE back
right there — no on_interaction listener, no view subclass, no state plumbing
built on a discord.py ``ui.View`` that internally ``await view.wait()``s and resolves to the
selected value. the caller never touches discord's interaction machinery: ``confirm`` returns
a bool (or None on timeout), ``choose`` returns the mapped value (or None). both scope to a
user, disable their components after resolve/timeout, and fail loud on send/edit errors while
treating a timeout as a normal None return.
"""
from __future__ import annotations
import logging
from typing import Any, Optional, Union
import discord
log = logging.getLogger(__name__)
_MAX_BUTTONS = 5
_NOT_FOR_YOU = "this prompt isn't for you"
Destination = Union[discord.abc.Messageable, discord.Interaction]
EmojiInput = Union[str, discord.Emoji, discord.PartialEmoji]
def _coerce_emoji(value: "Optional[EmojiInput]") -> "Optional[Union[str, discord.PartialEmoji]]":
"""accept a unicode emoji, a custom-emoji string '<:name:id>', or a discord Emoji/
PartialEmoji, and return what a component emoji= field accepts"""
if value is None:
return None
if isinstance(value, (discord.Emoji, discord.PartialEmoji)):
return value
if isinstance(value, str):
if value.startswith("<") and value.endswith(">"):
return discord.PartialEmoji.from_str(value)
return value
raise ValueError(f"unsupported emoji input: {type(value).__name__}")
def _infer_user(destination: Destination, user: "Optional[discord.abc.User]") -> "Optional[discord.abc.User]":
"""derive the invoker to scope the prompt to, from an explicit user, an Interaction, or a
ctx-like destination; None means anyone may click"""
if user is not None:
return user
if isinstance(destination, discord.Interaction):
return destination.user
author = getattr(destination, "author", None)
return author
class _ValueButton(discord.ui.Button):
"""a button that resolves the parent prompt to its bound value on click"""
def __init__(self, view: "_PromptView", value: Any, *, label=None, emoji=None, style=discord.ButtonStyle.secondary):
super().__init__(label=label, emoji=emoji, style=style)
self._prompt = view
self._value = value
async def callback(self, interaction: discord.Interaction) -> None:
"""resolve the prompt if the clicker is allowed, else tell them it isn't theirs"""
if not await self._prompt._allowed(interaction):
return
await interaction.response.defer()
self._prompt._resolve(self._value)
class _ValueSelect(discord.ui.Select):
"""a select whose chosen option resolves the parent prompt to a mapped value"""
def __init__(self, view: "_PromptView", mapping: "dict[str, Any]", placeholder: str):
super().__init__(placeholder=placeholder, min_values=1, max_values=1)
self._prompt = view
self._mapping = mapping
async def callback(self, interaction: discord.Interaction) -> None:
"""resolve to the mapped value of the selected option"""
if not await self._prompt._allowed(interaction):
return
await interaction.response.defer()
self._prompt._resolve(self._mapping[self.values[0]])
class _PromptView(discord.ui.View):
"""view backing an await-prompt: captures the resolved value and stops on first valid
interaction or timeout"""
def __init__(self, *, user: "Optional[discord.abc.User]", timeout: float):
super().__init__(timeout=timeout)
self._user = user
self.result: Any = None
async def _allowed(self, interaction: discord.Interaction) -> bool:
"""true when the clicker may resolve the prompt; otherwise send an ephemeral notice"""
if self._user is not None and interaction.user.id != self._user.id:
await interaction.response.send_message(_NOT_FOR_YOU, ephemeral=True)
return False
return True
def _resolve(self, value: Any) -> None:
"""record the result and stop waiting"""
self.result = value
self.stop()
async def _send_prompt(destination: Destination, content: str, view: discord.ui.View) -> discord.Message:
"""send the prompt to a Messageable or via an Interaction, returning the sent message"""
if isinstance(destination, discord.Interaction):
if destination.response.is_done():
return await destination.followup.send(content, view=view, wait=True)
await destination.response.send_message(content, view=view)
return await destination.original_response()
return await destination.send(content, view=view)
async def _finish(message: discord.Message, view: _PromptView, cleanup: bool) -> None:
"""disable the components and edit the message, or delete it when cleanup is set; edit/
delete failures on an already-gone message are not errors"""
if cleanup:
try:
await message.delete()
except discord.NotFound:
pass
return
for child in view.children:
child.disabled = True
try:
await message.edit(view=view)
except discord.NotFound:
pass
async def confirm(
destination: Destination,
prompt: str,
*,
user: "Optional[discord.abc.User]" = None,
timeout: float = 60.0,
yes: "EmojiInput" = "",
no: "EmojiInput" = "",
cleanup: bool = False,
) -> "Optional[bool]":
"""send prompt with a yes and a no button, await the click, and return True / False /
None(timeout); scopes to user, disables the buttons after, and fails loud on send/edit"""
scoped = _infer_user(destination, user)
view = _PromptView(user=scoped, timeout=timeout)
view.add_item(_ValueButton(view, True, emoji=_coerce_emoji(yes), style=discord.ButtonStyle.success))
view.add_item(_ValueButton(view, False, emoji=_coerce_emoji(no), style=discord.ButtonStyle.danger))
message = await _send_prompt(destination, prompt, view)
await view.wait()
await _finish(message, view, cleanup)
return view.result
async def choose(
destination: Destination,
prompt: str,
options: "dict[Any, Any]",
*,
user: "Optional[discord.abc.User]" = None,
timeout: float = 60.0,
cleanup: bool = False,
) -> "Optional[Any]":
"""render a button per option (or a select dropdown for >5 options or long labels), await
the click, and return the chosen option's VALUE or None(timeout); options maps an emoji
(unicode / '<:name:id>' / Emoji) or a label to a return value"""
if not options:
raise ValueError("choose requires at least one option")
scoped = _infer_user(destination, user)
view = _PromptView(user=scoped, timeout=timeout)
keys = list(options.keys())
use_select = len(keys) > _MAX_BUTTONS or any(_is_long_label(k) for k in keys)
if use_select:
mapping: "dict[str, Any]" = {}
select = _ValueSelect(view, mapping, placeholder=prompt[:100])
for i, key in enumerate(keys):
token = str(i)
mapping[token] = options[key]
label, emoji = _split_key(key)
select.add_option(label=label, value=token, emoji=emoji)
view.add_item(select)
else:
for key in keys:
label, emoji = _split_key(key)
view.add_item(_ValueButton(view, options[key], label=label if not emoji else None, emoji=emoji))
message = await _send_prompt(destination, prompt, view)
await view.wait()
await _finish(message, view, cleanup)
return view.result
def _is_long_label(key: Any) -> bool:
"""true when a plain-text label is too long for a button (>80 chars)"""
if isinstance(key, str) and not (key.startswith("<") and key.endswith(">")):
return len(key) > 80
return False
def _split_key(key: Any) -> "tuple[Optional[str], Optional[Union[str, discord.PartialEmoji]]]":
"""turn an options key into (label, emoji): an emoji-looking key becomes the emoji with no
label, everything else is a text label"""
if isinstance(key, (discord.Emoji, discord.PartialEmoji)):
return None, key
if isinstance(key, str):
if key.startswith("<") and key.endswith(">"):
return None, discord.PartialEmoji.from_str(key)
if _looks_unicode_emoji(key):
return None, key
return key, None
return str(key), None
def _looks_unicode_emoji(value: str) -> bool:
"""heuristic: a short non-ascii token is treated as a unicode emoji key"""
return bool(value) and len(value) <= 4 and not value.isascii()
+55
View File
@@ -0,0 +1,55 @@
"""limit-safe send that composes the text + embed helpers: chunk the content across
messages, fit every embed and split into <=10 groups, and send as many messages as needed"""
from __future__ import annotations
from typing import Optional
import discord
from .embeds import fit_embed, split_embeds
from .text import chunk_text
async def safe_send(
destination: discord.abc.Messageable,
content: "Optional[str]" = None,
*,
embeds: "Optional[list[discord.Embed]]" = None,
**kwargs,
) -> "list[discord.Message]":
"""send that never fails on a discord limit: chunk_text the content across messages,
fit_embed every embed and split_embeds into <=10 groups, and send as many messages as
needed; returns the sent Messages, fails loud on perms/HTTP errors (only limit-handling is
automatic)
extra kwargs ride only on the FIRST message so per-send options (files, reference, etc.)
aren't duplicated across the split"""
content_chunks = chunk_text(content) if content else []
embed_groups = split_embeds([fit_embed(embed) for embed in embeds]) if embeds else []
messages: "list[discord.Message]" = []
first = True
for i, chunk in enumerate(content_chunks):
# attach the first embed group to the last content message so a single content+embeds
# call collapses to one message when it fits
group = embed_groups.pop(0) if (i == len(content_chunks) - 1 and embed_groups) else None
messages.append(await _send(destination, chunk, group, first, kwargs))
first = False
for group in embed_groups:
messages.append(await _send(destination, None, group, first, kwargs))
first = False
if not messages:
messages.append(await _send(destination, content or "", None, True, kwargs))
return messages
async def _send(destination, content, embeds, first, kwargs) -> discord.Message:
"""send one message; first-message kwargs are applied once then dropped"""
extra = dict(kwargs) if first else {}
if embeds:
extra["embeds"] = embeds
return await destination.send(content=content, **extra)
+124
View File
@@ -0,0 +1,124 @@
"""text / formatting helpers: chunking over the message cap, monospace tables, discord
dynamic timestamps, and human-readable durations, plus the shared link-wrap / truncate
primitives the embed helpers reuse"""
from __future__ import annotations
import re
from datetime import datetime, timezone
from typing import Optional, Sequence
from .limits import MSG_LIMIT
_ELLIPSIS = ""
_TIMESTAMP_STYLES = ("t", "T", "d", "D", "f", "F", "R")
_URL_RE = re.compile(r"https?://[^\s<>]+", re.IGNORECASE)
_DISCORD_HOST_RE = re.compile(
r"^https?://(?:[a-z0-9-]+\.)*discord(?:app)?\.(?:com|gg|media)\b", re.IGNORECASE
)
def truncate(text: str, limit: int) -> str:
"""return text clipped to limit chars, appending an ellipsis where it cut"""
if len(text) <= limit:
return text
if limit <= 1:
return _ELLIPSIS[:limit]
return text[: limit - 1] + _ELLIPSIS
def wrap_bare_links(text: str) -> str:
"""wrap non-discord http(s) links in <> so discord does not unfurl them; discord links
(which the client renders specially) are left bare, and links already inside <> are not
double-wrapped"""
if not text:
return text
def repl(match: "re.Match[str]") -> str:
url = match.group(0)
start = match.start()
if start > 0 and text[start - 1] == "<":
return url
if _DISCORD_HOST_RE.match(url):
return url
return f"<{url}>"
return _URL_RE.sub(repl, text)
def chunk_text(text: str, limit: int = MSG_LIMIT) -> list[str]:
"""split text into pieces each <= limit, breaking on newline boundaries where possible,
then word boundaries, only mid-word as a last resort; no content is lost"""
if limit < 1:
raise ValueError(f"limit must be >= 1, got {limit}")
if len(text) <= limit:
return [text] if text else []
chunks: list[str] = []
remaining = text
while len(remaining) > limit:
window = remaining[:limit]
split = window.rfind("\n")
if split <= 0:
split = window.rfind(" ")
if split <= 0:
split = limit
chunks.append(remaining[:split])
remaining = remaining[split:]
if remaining.startswith(("\n", " ")):
remaining = remaining[1:]
if remaining:
chunks.append(remaining)
return chunks
def format_table(rows: "Sequence[Sequence]", headers: "Optional[Sequence]" = None) -> str:
"""render rows as an aligned monospace table wrapped in a code block, column widths
auto-sized; raises ValueError on ragged rows"""
matrix = [[str(cell) for cell in row] for row in rows]
width = len(headers) if headers is not None else (len(matrix[0]) if matrix else 0)
for i, row in enumerate(matrix):
if len(row) != width:
raise ValueError(f"row {i} has {len(row)} cells, expected {width}")
head = [str(h) for h in headers] if headers is not None else None
widths = [0] * width
for row in ([head] if head else []) + matrix:
for c, cell in enumerate(row):
widths[c] = max(widths[c], len(cell))
def render(row: "list[str]") -> str:
return " ".join(cell.ljust(widths[c]) for c, cell in enumerate(row)).rstrip()
lines = []
if head:
lines.append(render(head))
lines.append(" ".join("-" * widths[c] for c in range(width)))
lines.extend(render(row) for row in matrix)
return "```\n" + "\n".join(lines) + "\n```"
def discord_timestamp(dt: datetime, style: str = "f") -> str:
"""return discord's dynamic timestamp markup <t:unix:style> for a datetime; raises
ValueError on an invalid style (one of t/T/d/D/f/F/R)"""
if style not in _TIMESTAMP_STYLES:
raise ValueError(f"invalid timestamp style '{style}'; expected one of {_TIMESTAMP_STYLES}")
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
return f"<t:{int(dt.timestamp())}:{style}>"
def humanize_delta(seconds: float) -> str:
"""human-readable duration ('2h 5m', '3d 4h') from a seconds count; shows the two most
significant non-zero units, or '0s' for zero"""
total = int(abs(seconds))
sign = "-" if seconds < 0 else ""
units = (("d", 86400), ("h", 3600), ("m", 60), ("s", 1))
parts = []
for label, size in units:
if total >= size:
value, total = divmod(total, size)
parts.append(f"{value}{label}")
if not parts:
return "0s"
return sign + " ".join(parts[:2])