operator help: complete + context-aware (ops-17): !bot help full grouped list, !help for everyone per-channel
This commit is contained in:
@@ -182,6 +182,9 @@ class FjerkroaBot(commands.Bot):
|
||||
if content.startswith("!privacy"):
|
||||
await message.channel.send(self.config.get("privacy-notice", DEFAULT_PRIVACY_NOTICE), suppress_embeds=True)
|
||||
return
|
||||
if content.startswith("!help"): # OPS-17: context-aware, works even while paused
|
||||
await message.channel.send(self._help_text(staff=self.is_staff_channel(message.channel)), suppress_embeds=True)
|
||||
return
|
||||
if not self.replies_allowed():
|
||||
return
|
||||
if str(message.content).startswith("!wichtel"):
|
||||
@@ -263,15 +266,35 @@ class FjerkroaBot(commands.Bot):
|
||||
return f"Cancelled {store.task_set_state(int(args[1]), 'cancelled')} task(s)."
|
||||
return None
|
||||
|
||||
def _help_text(self, staff: bool) -> str:
|
||||
"""Context-aware command help (OPS-17): every channel lists the user commands; the staff channel also lists operator commands."""
|
||||
everywhere = (
|
||||
"Available to everyone, in any channel:\n"
|
||||
"• `!help` — this help\n"
|
||||
"• `!forgetme` — delete your messages and memory traces (works even while I'm paused)\n"
|
||||
"• `!privacy` — how your data is handled (works even while I'm paused)\n"
|
||||
"• `!wichtel @a @b @c …` — draw Secret Santa pairings (needs ≥2 mentions; only while I'm active)"
|
||||
)
|
||||
if not staff:
|
||||
return everywhere
|
||||
operator = (
|
||||
"Staff commands — this channel only, prefixed `!bot`:\n"
|
||||
"• Control: `pause`, `resume`, `quiet <minutes>`, `status`\n"
|
||||
"• Cost: `spend`, `images on|off`\n"
|
||||
"• Memory: `memory <user>`, `forget-fact <id>`, `pin <channel|global> <text>`, `unpin <id>`, `pins`\n"
|
||||
"• Tasks: `tasks` (list), `tasks on|off`, `task-approve <id>`, `task-cancel <id>`"
|
||||
)
|
||||
return operator + "\n\n" + everywhere
|
||||
|
||||
async def handle_staff_command(self, message: Message) -> None:
|
||||
"""Operator kill-switches, staff channel only (OPS-01..05, OPS-09, MEM-07)."""
|
||||
"""Operator kill-switches, staff channel only (OPS-01..05, OPS-09, OPS-17, MEM-07)."""
|
||||
args = str(message.content).split()[1:]
|
||||
for handler in (self._memory_command, self._task_command):
|
||||
reply = handler(args)
|
||||
if reply is not None:
|
||||
await message.channel.send(reply, suppress_embeds=True)
|
||||
return
|
||||
reply = "Commands: pause, resume, images on|off, tasks on|off, quiet <minutes>, status, spend, memory <user>, forget-fact <id>, pin <channel|global> <fact>, unpin <id>"
|
||||
reply = self._help_text(staff=True) # OPS-17: unknown/`help` -> full grouped help
|
||||
if args[:1] == ["pause"]:
|
||||
self.replies_enabled = False
|
||||
reply = "Replies paused."
|
||||
|
||||
@@ -73,3 +73,15 @@ per-channel) — without it, `!bot unpin <id>` required guessing ids.
|
||||
`!bot spend` answers in the staff channel with today's estimated
|
||||
spend in USD, token and image counts, and the configured budget.
|
||||
Management sees the cost, not just the cap.
|
||||
|
||||
### OPS-17 — Help is complete and context-aware (coverage: test)
|
||||
|
||||
Help reflects where each command actually works, because not every
|
||||
command is allowed everywhere. `!help` answers in any channel and
|
||||
lists only the commands usable there: in a normal channel the
|
||||
everyone-commands (`!help`, `!forgetme`, `!privacy`, `!wichtel`); in
|
||||
the staff channel it additionally lists the operator commands grouped
|
||||
by purpose (control, cost, memory, tasks). `!bot help` — and any
|
||||
unrecognised `!bot` command — answers with that same full staff help,
|
||||
so the listing is exhaustive rather than the old hand-maintained
|
||||
partial line. Help works even while the bot is paused.
|
||||
|
||||
@@ -133,3 +133,69 @@ class TestTasksKillSwitch(OpsBase):
|
||||
"""OPS-09: bot-initiated posts respect pause/quiet."""
|
||||
await self.bot.on_message(self.staff_msg("!bot pause"))
|
||||
self.assertFalse(self.bot.bot_initiated_allowed())
|
||||
|
||||
|
||||
class TestHelp(OpsBase):
|
||||
STAFF_CMDS = (
|
||||
"pause",
|
||||
"resume",
|
||||
"quiet <minutes>",
|
||||
"status",
|
||||
"spend",
|
||||
"images on|off",
|
||||
"memory <user>",
|
||||
"forget-fact <id>",
|
||||
"pin <channel|global>",
|
||||
"unpin <id>",
|
||||
"pins",
|
||||
"task-approve <id>",
|
||||
"task-cancel <id>",
|
||||
"(list)",
|
||||
)
|
||||
|
||||
def test_staff_help_is_complete_and_grouped(self):
|
||||
"""OPS-17: staff help lists every operator command, grouped by purpose."""
|
||||
text = self.bot._help_text(staff=True)
|
||||
for cmd in self.STAFF_CMDS:
|
||||
self.assertIn(cmd, text, f"missing {cmd!r} in staff help")
|
||||
for group in ("Control:", "Cost:", "Memory:", "Tasks:"):
|
||||
self.assertIn(group, text)
|
||||
for cmd in ("!help", "!forgetme", "!privacy", "!wichtel"):
|
||||
self.assertIn(cmd, text) # everywhere-commands shown too
|
||||
|
||||
def test_user_help_hides_operator_commands(self):
|
||||
"""OPS-17: non-staff help shows only the everyone-commands."""
|
||||
text = self.bot._help_text(staff=False)
|
||||
for cmd in ("!help", "!forgetme", "!privacy", "!wichtel"):
|
||||
self.assertIn(cmd, text)
|
||||
for op in ("task-approve", "images on|off", "spend", "Staff commands", "Control:"):
|
||||
self.assertNotIn(op, text)
|
||||
|
||||
async def test_bot_help_in_staff_channel_returns_full_help(self):
|
||||
"""OPS-17: `!bot help` answers with the complete staff help."""
|
||||
await self.bot.on_message(self.staff_msg("!bot help"))
|
||||
text = self.bot.staff_channel.send.await_args.args[0]
|
||||
self.assertIn("Staff commands", text)
|
||||
self.assertIn("task-cancel <id>", text)
|
||||
|
||||
async def test_unknown_bot_command_falls_back_to_help(self):
|
||||
"""OPS-17: an unrecognised `!bot` command shows the full help, not a partial line."""
|
||||
await self.bot.on_message(self.staff_msg("!bot wat"))
|
||||
text = self.bot.staff_channel.send.await_args.args[0]
|
||||
self.assertIn("Control:", text)
|
||||
|
||||
async def test_help_in_public_channel_is_user_scoped(self):
|
||||
"""OPS-17: `!help` in a normal channel lists only everyone-commands."""
|
||||
msg = self.public_msg("!help")
|
||||
await self.bot.on_message(msg)
|
||||
text = msg.channel.send.await_args.args[0]
|
||||
self.assertIn("!forgetme", text)
|
||||
self.assertNotIn("Staff commands", text)
|
||||
self.assertNotIn("task-approve", text)
|
||||
|
||||
async def test_help_works_while_paused(self):
|
||||
"""OPS-17: help answers even when replies are paused."""
|
||||
await self.bot.on_message(self.staff_msg("!bot pause"))
|
||||
msg = self.public_msg("!help")
|
||||
await self.bot.on_message(msg)
|
||||
msg.channel.send.assert_awaited()
|
||||
|
||||
Reference in New Issue
Block a user