From fc366f48fa8d155dfd21bf0ccf9108cbbf51580e Mon Sep 17 00:00:00 2001 From: veg Date: Sat, 4 Jul 2026 09:14:19 +0000 Subject: [PATCH] fix: let voice/mute/kick/detach take --party A host of two or more parties was told 'pass a name' by commands that accepted no name flag, making moderation impossible. Mirrors the --party flag invite already had. --- party | 66 ++++++++++++++++++++++++------------ tests/95-stub-roundtrip.bats | 27 +++++++++++++++ 2 files changed, 71 insertions(+), 22 deletions(-) diff --git a/party b/party index d74d07a..cb6e1ce 100755 --- a/party +++ b/party @@ -1034,19 +1034,43 @@ EOF echo "Invited $user to '$RR_PARTY_NAME'." } -# Toggle a user's read/write status. $1 = subcommand label (for usage -# and the success message), $2 = server-access flag (-w voice, -r mute), -# $3 = past-participle for the message, $4 = $user. -_set_user_access() { - label="$1" flag="$2" verb="$3" user="$4" - [ -n "$user" ] || { echo "Usage: party $label " >&2; exit 2; } - resolve_authoritative_party "" || exit 1 - "$PARTY_TMUX" -S "$RR_SOCKET" server-access "$flag" "$user" - echo "$verb $user." +# Parse the " [--party ]" argv shared by voice/mute/kick/ +# detach. Sets mod_user and mod_party in the calling shell (POSIX sh +# has no locals; the mod_ prefix keeps them out of callers' way). +# Exits 2 on usage errors. +_parse_user_party() { + label="$1"; shift + mod_user='' mod_party='' + while [ $# -gt 0 ]; do + case "$1" in + --party) + [ $# -ge 2 ] || { echo "party $label: --party needs an argument" >&2; exit 2; } + mod_party="$2"; shift 2 ;; + -h|--help) + echo "Usage: party $label [--party ]"; exit 0 ;; + -*) echo "party $label: unknown flag '$1'" >&2; exit 2 ;; + *) + if [ -z "$mod_user" ]; then mod_user="$1"; shift + else echo "party $label: unexpected arg '$1'" >&2; exit 2; fi + ;; + esac + done + [ -n "$mod_user" ] || { echo "Usage: party $label [--party ]" >&2; exit 2; } } -cmd_voice() { _set_user_access voice -w Voiced "${1:-}"; } -cmd_mute() { _set_user_access mute -r Muted "${1:-}"; } +# Toggle a user's read/write status. $1 = subcommand label (for usage +# and the success message), $2 = server-access flag (-w voice, -r mute), +# $3 = past-participle for the message; the rest is the user's argv. +_set_user_access() { + label="$1" flag="$2" verb="$3"; shift 3 + _parse_user_party "$label" "$@" + resolve_authoritative_party "$mod_party" || exit 1 + "$PARTY_TMUX" -S "$RR_SOCKET" server-access "$flag" "$mod_user" + echo "$verb $mod_user." +} + +cmd_voice() { _set_user_access voice -w Voiced "$@"; } +cmd_mute() { _set_user_access mute -r Muted "$@"; } # Disconnect every client of $user on the resolved party socket and kill # their guest session if present. Used by both cmd_kick and cmd_detach. @@ -1061,20 +1085,18 @@ _disconnect_user() { } cmd_kick() { - user="${1:-}" - [ -n "$user" ] || { echo "Usage: party kick " >&2; exit 2; } - resolve_authoritative_party "" || exit 1 - "$PARTY_TMUX" -S "$RR_SOCKET" server-access -d "$user" 2>/dev/null || true - _disconnect_user "$user" - echo "Kicked $user." + _parse_user_party kick "$@" + resolve_authoritative_party "$mod_party" || exit 1 + "$PARTY_TMUX" -S "$RR_SOCKET" server-access -d "$mod_user" 2>/dev/null || true + _disconnect_user "$mod_user" + echo "Kicked $mod_user." } cmd_detach() { - user="${1:-}" - [ -n "$user" ] || { echo "Usage: party detach " >&2; exit 2; } - resolve_authoritative_party "" || exit 1 - _disconnect_user "$user" - echo "Detached $user (still on allowlist)." + _parse_user_party detach "$@" + resolve_authoritative_party "$mod_party" || exit 1 + _disconnect_user "$mod_user" + echo "Detached $mod_user (still on allowlist)." } cmd_list() { diff --git a/tests/95-stub-roundtrip.bats b/tests/95-stub-roundtrip.bats index 629db74..e327073 100644 --- a/tests/95-stub-roundtrip.bats +++ b/tests/95-stub-roundtrip.bats @@ -810,3 +810,30 @@ STUB ! grep -q "server-access -aw $USER" "$TMUX_LOG" || \ fail "should not have attempted -aw before capability check" } + +@test "moderation accepts --party to disambiguate multiple hosted parties" { + "$PARTY_BIN" host alpha + "$PARTY_BIN" host bravo + + # Without --party: ambiguous, must refuse and say so. + run "$PARTY_BIN" kick nobody + [ "$status" -ne 0 ] + [[ "$output" == *"multiple parties"* ]] + + # With --party: resolves, and the server-access -d lands on bravo's + # socket specifically. + : > "$TMUX_LOG" + run "$PARTY_BIN" kick nobody --party bravo + [ "$status" -eq 0 ] + grep "server-access -d nobody" "$TMUX_LOG" | grep -q "party-$USER:bravo.d/sock" + + : > "$TMUX_LOG" + run "$PARTY_BIN" voice nobody --party alpha + [ "$status" -eq 0 ] + grep "server-access -w nobody" "$TMUX_LOG" | grep -q "party-$USER:alpha.d/sock" + + run "$PARTY_BIN" mute nobody --party alpha + [ "$status" -eq 0 ] + run "$PARTY_BIN" detach nobody --party alpha + [ "$status" -eq 0 ] +}