Merge pull request #383 from shazow/add-mute

chat: Add /mute command for op
This commit is contained in:
Andrey Petrov 2021-04-13 11:24:53 -04:00 committed by GitHub
commit 9bf66ea992
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 158 additions and 2 deletions

View File

@ -477,4 +477,38 @@ func InitCommands(c *Commands) {
return nil
},
})
c.Add(Command{
Op: true,
Prefix: "/mute",
PrefixHelp: "USER",
Help: "Toggle muting USER, preventing messages from broadcasting.",
Handler: func(room *Room, msg message.CommandMsg) error {
if !room.IsOp(msg.From()) {
return errors.New("must be op")
}
args := msg.Args()
if len(args) == 0 {
return errors.New("must specify user")
}
member, ok := room.MemberByID(args[0])
if !ok {
return errors.New("user not found")
}
setMute := !member.IsMuted()
member.SetMute(setMute)
id := member.ID()
if setMute {
room.Send(message.NewSystemMsg("Muted: "+id, msg.From()))
} else {
room.Send(message.NewSystemMsg("Unmuted: "+id, msg.From()))
}
return nil
},
})
}

View File

@ -1,7 +1,9 @@
package chat
import "io"
import stdlog "log"
import (
"io"
stdlog "log"
)
var logger *stdlog.Logger

View File

@ -27,6 +27,23 @@ var ErrInvalidName = errors.New("invalid name")
type Member struct {
*message.User
IsOp bool
// TODO: Move IsOp under mu?
mu sync.Mutex
isMuted bool // When true, messages should not be broadcasted.
}
func (m *Member) IsMuted() bool {
m.mu.Lock()
defer m.mu.Unlock()
return m.isMuted
}
func (m *Member) SetMute(muted bool) {
m.mu.Lock()
defer m.mu.Unlock()
m.isMuted = muted
}
// Room definition, also a Set of User Items
@ -84,6 +101,20 @@ func (r *Room) HandleMsg(m message.Message) {
fromID = fromMsg.From().ID()
}
if fromID != "" {
if item, err := r.Members.Get(fromID); err != nil {
// Message from a member who is not in the room, this should not happen.
logger.Printf("Room received unexpected message from a non-member: %v", m)
return
} else if member, ok := item.Value().(*Member); ok && member.IsMuted() {
// Short circuit message handling for muted users
if _, ok = m.(*message.CommandMsg); !ok {
member.User.Send(m)
}
return
}
}
switch m := m.(type) {
case *message.CommandMsg:
cmd := *m
@ -150,6 +181,7 @@ func (r *Room) Join(u *message.User) (*Member, error) {
if err != nil {
return nil, err
}
// TODO: Remove user ID from sets, probably referring to a prior user.
r.History(u)
s := fmt.Sprintf("%s joined. (Connected: %d)", u.Name(), r.Members.Len())
r.Send(message.NewAnnounceMsg(s))

View File

@ -158,6 +158,94 @@ func TestIgnore(t *testing.T) {
expectOutput(t, buffer, ignored.user.Name()+": hello again!"+message.Newline)
}
func TestMute(t *testing.T) {
var buffer []byte
ch := NewRoom()
go ch.Serve()
defer ch.Close()
// Create 3 users, join the room and clear their screen buffers
users := make([]ScreenedUser, 3)
members := make([]*Member, 3)
for i := 0; i < 3; i++ {
screen := &MockScreen{}
user := message.NewUserScreen(message.SimpleID(fmt.Sprintf("user%d", i)), screen)
users[i] = ScreenedUser{
user: user,
screen: screen,
}
member, err := ch.Join(user)
if err != nil {
t.Fatal(err)
}
members[i] = member
}
for _, u := range users {
for i := 0; i < 3; i++ {
u.user.HandleMsg(u.user.ConsumeOne())
u.screen.Read(&buffer)
}
}
// Use some handy variable names for distinguish between roles
muter := users[0]
muted := users[1]
other := users[2]
members[0].IsOp = true
// test muting unexisting user
if err := sendCommand("/mute test", muter, ch, &buffer); err != nil {
t.Fatal(err)
}
expectOutput(t, buffer, "-> Err: user not found"+message.Newline)
// test muting by non-op
if err := sendCommand("/mute "+muted.user.Name(), other, ch, &buffer); err != nil {
t.Fatal(err)
}
expectOutput(t, buffer, "-> Err: must be op"+message.Newline)
// test muting existing user
if err := sendCommand("/mute "+muted.user.Name(), muter, ch, &buffer); err != nil {
t.Fatal(err)
}
expectOutput(t, buffer, "-> Muted: "+muted.user.Name()+message.Newline)
if got, want := members[1].IsMuted(), true; got != want {
t.Error("muted user failed to set mute flag")
}
// when an emote is sent by a muted user, it should not be displayed for anyone
ch.HandleMsg(message.NewPublicMsg("hello!", muted.user))
ch.HandleMsg(message.NewEmoteMsg("is crying", muted.user))
if muter.user.HasMessages() {
muter.user.HandleMsg(muter.user.ConsumeOne())
muter.screen.Read(&buffer)
t.Errorf("muter should not have messages: %s", buffer)
}
if other.user.HasMessages() {
other.user.HandleMsg(other.user.ConsumeOne())
other.screen.Read(&buffer)
t.Errorf("other should not have messages: %s", buffer)
}
// test unmuting
if err := sendCommand("/mute "+muted.user.Name(), muter, ch, &buffer); err != nil {
t.Fatal(err)
}
expectOutput(t, buffer, "-> Unmuted: "+muted.user.Name()+message.Newline)
ch.HandleMsg(message.NewPublicMsg("hello again!", muted.user))
other.user.HandleMsg(other.user.ConsumeOne())
other.screen.Read(&buffer)
expectOutput(t, buffer, muted.user.Name()+": hello again!"+message.Newline)
}
func expectOutput(t *testing.T, buffer []byte, expected string) {
t.Helper()