- Add rubberband package to container for pitch-shifting FX - Split FX chain: rubberband CLI for pitch, ffmpeg for filters - Configurable voice profile (voice, fx, piper params) in [voice] - Extra bots inherit voice config (minus trigger) for own TTS - Greeting is voice-only, spoken directly by the greeting bot - Per-bot only_plugins/except_plugins filtering on Mumble - Alias plugin, core plugin tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
86 lines
2.7 KiB
Python
86 lines
2.7 KiB
Python
"""Plugin: user-defined command aliases (persistent)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from derp.plugin import command
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
_NS = "alias"
|
|
|
|
|
|
@command("alias", help="Aliases: !alias add|del|list|clear")
|
|
async def cmd_alias(bot, message):
|
|
"""Create short aliases for existing bot commands.
|
|
|
|
Usage:
|
|
!alias add <name> <target> Create alias (e.g. !alias add s skip)
|
|
!alias del <name> Remove alias
|
|
!alias list Show all aliases
|
|
!alias clear Remove all aliases (admin only)
|
|
"""
|
|
parts = message.text.split(None, 3)
|
|
if len(parts) < 2:
|
|
await bot.reply(message, "Usage: !alias <add|del|list|clear> [args]")
|
|
return
|
|
|
|
sub = parts[1].lower()
|
|
|
|
if sub == "add":
|
|
if len(parts) < 4:
|
|
await bot.reply(message, "Usage: !alias add <name> <target>")
|
|
return
|
|
name = parts[2].lower()
|
|
target = parts[3].lower()
|
|
|
|
# Cannot shadow an existing registered command
|
|
if name in bot.registry.commands:
|
|
await bot.reply(message, f"'{name}' is already a registered command")
|
|
return
|
|
|
|
# Cannot alias to another alias (single-level only)
|
|
if bot.state.get(_NS, target) is not None:
|
|
await bot.reply(message, f"'{target}' is itself an alias; no chaining")
|
|
return
|
|
|
|
# Target must resolve to a real command
|
|
if target not in bot.registry.commands:
|
|
await bot.reply(message, f"unknown command: {target}")
|
|
return
|
|
|
|
bot.state.set(_NS, name, target)
|
|
await bot.reply(message, f"alias: {name} -> {target}")
|
|
|
|
elif sub == "del":
|
|
if len(parts) < 3:
|
|
await bot.reply(message, "Usage: !alias del <name>")
|
|
return
|
|
name = parts[2].lower()
|
|
if bot.state.delete(_NS, name):
|
|
await bot.reply(message, f"alias removed: {name}")
|
|
else:
|
|
await bot.reply(message, f"no alias: {name}")
|
|
|
|
elif sub == "list":
|
|
keys = bot.state.keys(_NS)
|
|
if not keys:
|
|
await bot.reply(message, "No aliases defined")
|
|
return
|
|
entries = []
|
|
for key in sorted(keys):
|
|
target = bot.state.get(_NS, key)
|
|
entries.append(f"{key} -> {target}")
|
|
await bot.reply(message, "Aliases: " + ", ".join(entries))
|
|
|
|
elif sub == "clear":
|
|
if not bot._is_admin(message):
|
|
await bot.reply(message, "Permission denied: clear requires admin")
|
|
return
|
|
count = bot.state.clear(_NS)
|
|
await bot.reply(message, f"Cleared {count} alias(es)")
|
|
|
|
else:
|
|
await bot.reply(message, "Usage: !alias <add|del|list|clear> [args]")
|