Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 18 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,18 @@ All commands live under one group, `matr`, invoked with a per-guild prefix
pinned forever, and busy channels nudge it: the score includes the
rolling count of channel messages over the last 10 minutes (a small,
bounded in-memory window fed by every observed message, bots included).
- **Bot-directed quips** — aiming an action at Matrimony itself appends a
rotating one-liner as Discord subtext (a `-# ` line) under the normal
reply, and proposals at the bot are declined outright ("sorry, taken by
discord.py").
- **Ambient quips everywhere** — basically every command reply ends with
one rotating one-liner of commentary, rendered as Discord subtext (a
`-# ` line) at the bottom of the embed or message. All quips live in a
single file (`matrimony/quips.py`) organized by command and theme:
marriage, divorce, trees, blocks, law, admin, fun actions, meta, and
more.
- **Bot-directed quips** — aiming an action at Matrimony itself swaps the
ambient line for one from the bot's own pool, and proposals at the bot
are declined outright ("sorry, taken by discord.py").
- **Unknown-action quips** — `!matr blorp` (or any nonexistent action)
gets a rotating funny quip plus the help embed, never silence. The
bot's own messages never trigger it.
gets a rotating funny quip as subtext plus the help embed, never
silence. The bot's own messages never trigger it.
- **Marital wisdom** — `!matr help` (and bare `!matr`) ends with one
randomly chosen quip about memes, Discord culture, programming, and
relationships, rendered as Discord subtext below the full command
Expand All @@ -87,16 +92,17 @@ explicitly supports bot-authored commands and bot targets:
self-command loops while leaving every other bot free to participate.
- Command parsing is lenient: leading/trailing whitespace and extra text
after the arguments are tolerated, `@Matrimony matr ...` works as a
mention prefix, and whitespace after the prefix (`! matr help`) is
accepted.
mention prefix, whitespace after the prefix (`! matr help`) is
accepted, and the command may sit on **any line** of the message —
chatter first, then `!matr <action>` on its own line, still runs.
- Covered by tests: proposal/accept flows driven by bot-authored commands
(`tests/test_dispatch.py`).

## Command reference

| Command | Description |
| --- | --- |
| `!matr help` | Show the command list |
| `!matr help [bot]` | Show the command list; `help bot` shows bot-specific guidance |
| `!matr marry @user` | Propose to a user or bot (existing partners get an accept/divorce notice) |
| `!matr divorce [@user]` | Divorce a partner (required if you have several) |
| `!matr accept [@proposer]` / `decline` | Answer a pending proposal — `accept` also acknowledges partner notices |
Expand Down Expand Up @@ -126,7 +132,9 @@ explicitly supports bot-authored commands and bot targets:
| `!matr anniversary [@partner]` (alias `anniv`) | Time married (+ GIF) |
| `!matr info` / `invite` / `stats` | Bot information |

`@Matrimony matr ...` (mention prefix) also always works.
`@Matrimony matr ...` (mention prefix) also always works, and the
command doesn't have to open the message — any line that starts with
the prefix is parsed as the command (everything else is chatter).

On the slash side, `restrain` is `/matr restrain` and `clear` lives under
the admin subgroup as `/matr admin clear`; `anniversary` is under
Expand Down
24 changes: 21 additions & 3 deletions matrimony/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,18 +76,36 @@ def should_process(self, message: discord.Message) -> bool:
allowed = self.config.allowed_channels.get(guild.id)
return allowed is None or message.channel.id in allowed

@staticmethod
def _command_line(content: str, prefixes: list[str]) -> str | None:
"""The first line of ``content`` starting with a command prefix.

Commands don't have to open the message: chatter may come first
and the command can sit on any later line ("hey all\\n!matr
partners"). The rest of the message is commentary, not arguments.
Leading whitespace on the command line is tolerated."""
for line in content.splitlines():
candidate = line.lstrip()
if any(candidate.startswith(prefix) for prefix in prefixes):
return candidate
return None

async def process_commands(self, message: discord.Message) -> None:
"""Dispatch ``message`` as a command.

discord.py's built-in version silently drops every message whose
author is a bot; bots are first-class users here, so the parser only
skips this bot's own messages (``should_process``). Stray whitespace
around the command text is tolerated."""
around the command text is tolerated, and a command works from any
line of the message, not just the first."""
if not self.should_process(message):
return
content = message.content.strip()
if content != message.content:
message.content = content
prefixes = await self.get_prefix(message)
command_line = self._command_line(content, prefixes)
message.content = (
command_line if command_line is not None else content
)
ctx = await self.get_context(message)
await self.invoke(ctx)

Expand Down
Loading
Loading