5 Commits
5 changed files with 304 additions and 2 deletions
+8
View File
@@ -82,6 +82,14 @@ Each event has the following parameters:
* event: the name of the event * event: the name of the event
- join: user has joined the channel you are in - join: user has joined the channel you are in
- leave: user has left the channel you are in - leave: user has left the channel you are in
- mute: a user in your channel has muted themselves
- unmute: a user in your channel has unmuted themselves
- deafen: a user in your channel has deafened themselves
- undeafen: a user in your channel has undeafened themselves
- mutedeafen: a user in your channel has muted and deafened themselves
- unmuteundeafen: a user in your channel has unmuted and undeafened themselves
- unmutedeafen: a user in your channel has unmuted and deafened themselves
- muteundeafen: a user in your channel has muted and undeafened themselves
- micup: you have begun transmitting - micup: you have begun transmitting
- micdown: you have stopped transmitting - micdown: you have stopped transmitting
- connect: you have connected to a server - connect: you have connected to a server
+3 -2
View File
@@ -52,8 +52,9 @@ type Barnard struct {
exitMessage string exitMessage string
// Added for channel muting // Added for channel muting
MutedChannels map[uint32]bool MutedChannels map[uint32]bool
userChannels map[uint32]*gumble.Channel userChannels map[uint32]*gumble.Channel
userAudioStates map[uint32]userAudioState
// Added for noise suppression // Added for noise suppression
NoiseSuppressor *noise.Suppressor NoiseSuppressor *noise.Suppressor
+97
View File
@@ -78,6 +78,7 @@ func (b *Barnard) OnConnect(e *gumble.ConnectEvent) {
// Reset muted channels state on connect // Reset muted channels state on connect
b.MutedChannels = make(map[uint32]bool) b.MutedChannels = make(map[uint32]bool)
b.userChannels = make(map[uint32]*gumble.Channel) b.userChannels = make(map[uint32]*gumble.Channel)
b.userAudioStates = make(map[uint32]userAudioState)
b.RecordingMutex.Lock() b.RecordingMutex.Lock()
b.recordingAllowed = nil b.recordingAllowed = nil
b.recordingStarting = false b.recordingStarting = false
@@ -90,6 +91,7 @@ func (b *Barnard) OnConnect(e *gumble.ConnectEvent) {
for _, u := range b.Client.Users { for _, u := range b.Client.Users {
b.UserConfig.UpdateUser(u) b.UserConfig.UpdateUser(u)
b.rememberUserChannel(u) b.rememberUserChannel(u)
b.rememberUserAudioState(u)
} }
b.UpdateInputStatus(fmt.Sprintf("[%s]", e.Client.Self.Channel.Name)) b.UpdateInputStatus(fmt.Sprintf("[%s]", e.Client.Self.Channel.Name))
@@ -171,6 +173,7 @@ func (b *Barnard) OnTextMessage(e *gumble.TextMessageEvent) {
func (b *Barnard) OnUserChange(e *gumble.UserChangeEvent) { func (b *Barnard) OnUserChange(e *gumble.UserChangeEvent) {
notification, hasNotification := b.userChangeNotification(e) notification, hasNotification := b.userChangeNotification(e)
audioNotification, hasAudioNotification := b.userAudioChangeNotification(e)
if e.User != nil { if e.User != nil {
b.UserConfig.UpdateUser(e.User) b.UserConfig.UpdateUser(e.User)
@@ -198,6 +201,10 @@ func (b *Barnard) OnUserChange(e *gumble.UserChangeEvent) {
b.Notify(notification.event, notification.who, notification.what) b.Notify(notification.event, notification.who, notification.what)
b.AddOutputLine(notification.line) b.AddOutputLine(notification.line)
} }
if hasAudioNotification {
b.Notify(audioNotification.event, audioNotification.who, audioNotification.what)
b.AddOutputLine(audioNotification.line)
}
if e.Type.Has(gumble.UserChangeChannel) && e.User == b.Client.Self { if e.Type.Has(gumble.UserChangeChannel) && e.User == b.Client.Self {
b.UpdateInputStatus(fmt.Sprintf("[%s]", e.User.Channel.Name)) b.UpdateInputStatus(fmt.Sprintf("[%s]", e.User.Channel.Name))
} }
@@ -215,6 +222,7 @@ func (b *Barnard) OnUserChange(e *gumble.UserChangeEvent) {
b.AddOutputLine(formatUserStats(e.User)) b.AddOutputLine(formatUserStats(e.User))
} }
b.updateUserChannel(e) b.updateUserChannel(e)
b.updateUserAudioState(e)
b.RebuildUserChannelTreePreservingSelection() b.RebuildUserChannelTreePreservingSelection()
b.Ui.Refresh() b.Ui.Refresh()
} }
@@ -226,6 +234,11 @@ type userChangeNotification struct {
line string line string
} }
type userAudioState struct {
selfMuted bool
selfDeafened bool
}
func (b *Barnard) userChangeNotification(e *gumble.UserChangeEvent) (userChangeNotification, bool) { func (b *Barnard) userChangeNotification(e *gumble.UserChangeEvent) (userChangeNotification, bool) {
if e == nil || e.User == nil || b.Client == nil || b.Client.Self == nil || b.Client.Self.Channel == nil { if e == nil || e.User == nil || b.Client == nil || b.Client.Self == nil || b.Client.Self.Channel == nil {
return userChangeNotification{}, false return userChangeNotification{}, false
@@ -256,6 +269,66 @@ func (b *Barnard) userChangeNotification(e *gumble.UserChangeEvent) (userChangeN
return userChangeNotification{}, false return userChangeNotification{}, false
} }
func (b *Barnard) userAudioChangeNotification(e *gumble.UserChangeEvent) (userChangeNotification, bool) {
if e == nil || e.User == nil || !e.Type.Has(gumble.UserChangeAudio) || b.Client == nil || b.Client.Self == nil || b.Client.Self.Channel == nil {
return userChangeNotification{}, false
}
previous, known := b.userAudioStates[e.User.Session]
if !known || !sameChannel(e.User.Channel, b.Client.Self.Channel) {
return userChangeNotification{}, false
}
muteEvent := ""
muteVerb := ""
if previous.selfMuted != e.User.SelfMuted {
if e.User.SelfMuted {
muteEvent = "mute"
muteVerb = "muted"
} else {
muteEvent = "unmute"
muteVerb = "unmuted"
}
}
deafenEvent := ""
deafenVerb := ""
if previous.selfDeafened != e.User.SelfDeafened {
if e.User.SelfDeafened {
deafenEvent = "deafen"
deafenVerb = "deafened"
} else {
deafenEvent = "undeafen"
deafenVerb = "undeafened"
}
}
event := muteEvent + deafenEvent
verbs := make([]string, 0, 2)
if muteVerb != "" {
verbs = append(verbs, muteVerb)
}
if deafenVerb != "" {
verbs = append(verbs, deafenVerb)
}
if event == "" {
return userChangeNotification{}, false
}
verb := strings.Join(verbs, " and ")
who := e.User.Name
line := fmt.Sprintf("%s %s", e.User.Name, verb)
if e.User.Session == b.Client.Self.Session {
who = "me"
line = fmt.Sprintf("You %s yourself", verb)
}
return userChangeNotification{
event: event,
who: who,
what: e.User.Channel.Name,
line: line,
}, true
}
func buildUserChangeNotification(event string, verb string, user *gumble.User, eventChannel *gumble.Channel, currentChannel *gumble.Channel) (userChangeNotification, bool) { func buildUserChangeNotification(event string, verb string, user *gumble.User, eventChannel *gumble.Channel, currentChannel *gumble.Channel) (userChangeNotification, bool) {
if !sameChannel(eventChannel, currentChannel) { if !sameChannel(eventChannel, currentChannel) {
return userChangeNotification{}, false return userChangeNotification{}, false
@@ -302,6 +375,30 @@ func (b *Barnard) updateUserChannel(e *gumble.UserChangeEvent) {
b.rememberUserChannel(e.User) b.rememberUserChannel(e.User)
} }
func (b *Barnard) rememberUserAudioState(user *gumble.User) {
if user == nil {
return
}
if b.userAudioStates == nil {
b.userAudioStates = make(map[uint32]userAudioState)
}
b.userAudioStates[user.Session] = userAudioState{
selfMuted: user.SelfMuted,
selfDeafened: user.SelfDeafened,
}
}
func (b *Barnard) updateUserAudioState(e *gumble.UserChangeEvent) {
if e == nil || e.User == nil {
return
}
if e.Type.Has(gumble.UserChangeDisconnected) {
delete(b.userAudioStates, e.User.Session)
return
}
b.rememberUserAudioState(e.User)
}
func (b *Barnard) OnChannelChange(e *gumble.ChannelChangeEvent) { func (b *Barnard) OnChannelChange(e *gumble.ChannelChangeEvent) {
b.UpdateInputStatus(fmt.Sprintf("[%s]", e.Channel.Name)) b.UpdateInputStatus(fmt.Sprintf("[%s]", e.Channel.Name))
if e.Type.Has(gumble.ChannelChangeDescription) { if e.Type.Has(gumble.ChannelChangeDescription) {
+132
View File
@@ -144,3 +144,135 @@ func TestUpdateUserChannel(t *testing.T) {
t.Fatalf("expected disconnected user channel to be removed, got %#v", got) t.Fatalf("expected disconnected user channel to be removed, got %#v", got)
} }
} }
func TestUserAudioChangeNotification(t *testing.T) {
current := &gumble.Channel{ID: 1, Name: "Current"}
other := &gumble.Channel{ID: 2, Name: "Other"}
self := &gumble.User{Session: 1, Name: "Username", Channel: current}
tests := []struct {
name string
user *gumble.User
previous userAudioState
remember bool
change gumble.UserChangeType
want userChangeNotification
wantOK bool
}{
{
name: "user mutes",
user: &gumble.User{Session: 2, Name: "Guest", Channel: current, SelfMuted: true},
remember: true,
change: gumble.UserChangeAudio,
want: userChangeNotification{event: "mute", who: "Guest", what: "Current", line: "Guest muted"},
wantOK: true,
},
{
name: "user unmutes",
user: &gumble.User{Session: 2, Name: "Guest", Channel: current},
previous: userAudioState{selfMuted: true},
remember: true,
change: gumble.UserChangeAudio,
want: userChangeNotification{event: "unmute", who: "Guest", what: "Current", line: "Guest unmuted"},
wantOK: true,
},
{
name: "user deafens without mute change",
user: &gumble.User{Session: 2, Name: "Guest", Channel: current, SelfMuted: true, SelfDeafened: true},
previous: userAudioState{selfMuted: true},
remember: true,
change: gumble.UserChangeAudio,
want: userChangeNotification{event: "deafen", who: "Guest", what: "Current", line: "Guest deafened"},
wantOK: true,
},
{
name: "user undeafens without mute change",
user: &gumble.User{Session: 2, Name: "Guest", Channel: current, SelfMuted: true},
previous: userAudioState{selfMuted: true, selfDeafened: true},
remember: true,
change: gumble.UserChangeAudio,
want: userChangeNotification{event: "undeafen", who: "Guest", what: "Current", line: "Guest undeafened"},
wantOK: true,
},
{
name: "user mutes and deafens",
user: &gumble.User{Session: 2, Name: "Guest", Channel: current, SelfMuted: true, SelfDeafened: true},
remember: true,
change: gumble.UserChangeAudio,
want: userChangeNotification{event: "mutedeafen", who: "Guest", what: "Current", line: "Guest muted and deafened"},
wantOK: true,
},
{
name: "user unmutes and undeafens",
user: &gumble.User{Session: 2, Name: "Guest", Channel: current},
previous: userAudioState{selfMuted: true, selfDeafened: true},
remember: true,
change: gumble.UserChangeAudio,
want: userChangeNotification{event: "unmuteundeafen", who: "Guest", what: "Current", line: "Guest unmuted and undeafened"},
wantOK: true,
},
{
name: "self mutes and deafens",
user: &gumble.User{Session: 1, Name: "Username", Channel: current, SelfMuted: true, SelfDeafened: true},
remember: true,
change: gumble.UserChangeAudio,
want: userChangeNotification{event: "mutedeafen", who: "me", what: "Current", line: "You muted and deafened yourself"},
wantOK: true,
},
{
name: "other channel is ignored",
user: &gumble.User{Session: 2, Name: "Guest", Channel: other, SelfMuted: true},
remember: true,
change: gumble.UserChangeAudio,
},
{
name: "unknown initial state is ignored",
user: &gumble.User{Session: 2, Name: "Guest", Channel: current, SelfMuted: true},
change: gumble.UserChangeConnected | gumble.UserChangeAudio,
},
{
name: "unrelated change is ignored",
user: &gumble.User{Session: 2, Name: "Guest", Channel: current, SelfMuted: true},
remember: true,
change: gumble.UserChangeComment,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b := &Barnard{Client: &gumble.Client{Self: self}}
if tt.remember {
b.userAudioStates = map[uint32]userAudioState{tt.user.Session: tt.previous}
}
got, ok := b.userAudioChangeNotification(&gumble.UserChangeEvent{
Client: b.Client,
Type: tt.change,
User: tt.user,
})
if ok != tt.wantOK {
t.Fatalf("expected ok %v, got %v", tt.wantOK, ok)
}
if got != tt.want {
t.Fatalf("expected %#v, got %#v", tt.want, got)
}
})
}
}
func TestUpdateUserAudioState(t *testing.T) {
user := &gumble.User{Session: 2, SelfMuted: true, SelfDeafened: true}
b := &Barnard{}
b.updateUserAudioState(&gumble.UserChangeEvent{User: user})
if got := b.userAudioStates[user.Session]; got != (userAudioState{selfMuted: true, selfDeafened: true}) {
t.Fatalf("unexpected remembered audio state: %#v", got)
}
b.updateUserAudioState(&gumble.UserChangeEvent{
Type: gumble.UserChangeDisconnected,
User: user,
})
if _, ok := b.userAudioStates[user.Session]; ok {
t.Fatal("expected disconnected user audio state to be removed")
}
}
+64
View File
@@ -54,6 +54,70 @@ leave() {
[[ $notify ]] && notify "$2 left the channel." [[ $notify ]] && notify "$2 left the channel."
} }
mute() {
if [[ "$2" == "me" ]]; then
[[ $notify ]] && notify "You muted yourself."
else
[[ $notify ]] && notify "$2 muted."
fi
}
unmute() {
if [[ "$2" == "me" ]]; then
[[ $notify ]] && notify "You unmuted yourself."
else
[[ $notify ]] && notify "$2 unmuted."
fi
}
deafen() {
if [[ "$2" == "me" ]]; then
[[ $notify ]] && notify "You deafened yourself."
else
[[ $notify ]] && notify "$2 deafened."
fi
}
undeafen() {
if [[ "$2" == "me" ]]; then
[[ $notify ]] && notify "You undeafened yourself."
else
[[ $notify ]] && notify "$2 undeafened."
fi
}
mutedeafen() {
if [[ "$2" == "me" ]]; then
[[ $notify ]] && notify "You muted and deafened yourself."
else
[[ $notify ]] && notify "$2 muted and deafened."
fi
}
unmuteundeafen() {
if [[ "$2" == "me" ]]; then
[[ $notify ]] && notify "You unmuted and undeafened yourself."
else
[[ $notify ]] && notify "$2 unmuted and undeafened."
fi
}
unmutedeafen() {
if [[ "$2" == "me" ]]; then
[[ $notify ]] && notify "You unmuted and deafened yourself."
else
[[ $notify ]] && notify "$2 unmuted and deafened."
fi
}
muteundeafen() {
if [[ "$2" == "me" ]]; then
[[ $notify ]] && notify "You muted and undeafened yourself."
else
[[ $notify ]] && notify "$2 muted and undeafened."
fi
}
micdown() { micdown() {
[[ $sound ]] && play -qnV0 synth .25 sin G6:E5 norm -8 [[ $sound ]] && play -qnV0 synth .25 sin G6:E5 norm -8
[[ $notify ]] && notify "You have stopped transmitting." [[ $notify ]] && notify "You have stopped transmitting."