diff --git a/irc/channel.go b/irc/channel.go index a771b8a0..05a96459 100644 --- a/irc/channel.go +++ b/irc/channel.go @@ -6,15 +6,14 @@ import ( ) type Channel struct { - flags ChannelModeSet - lists map[ChannelMode]*UserMaskSet - key Text - members MemberSet - name Name - server *Server - topic Text - userLimit uint64 - theaterUser *Client + flags ChannelModeSet + lists map[ChannelMode]*UserMaskSet + key Text + members MemberSet + name Name + server *Server + topic Text + userLimit uint64 } // NewChannel creates a new channel from a `Server` and a `name` @@ -406,9 +405,6 @@ func (channel *Channel) applyMode(client *Client, change *ChannelModeChange) boo return channel.applyModeMember(client, change.mode, change.op, NewName(change.arg)) - case Theater: - client.ErrConfiguredMode(change.mode) - default: client.ErrUnknownMode(change.mode, channel) } diff --git a/irc/client.go b/irc/client.go index 26634405..4e076b54 100644 --- a/irc/client.go +++ b/irc/client.go @@ -13,28 +13,27 @@ const ( ) type Client struct { - atime time.Time - authorized bool - awayMessage Text - capabilities CapabilitySet - capState CapState - channels ChannelSet - commands chan Command - ctime time.Time - flags map[UserMode]bool - hasQuit bool - hops uint - hostname Name - idleTimer *time.Timer - loginTimer *time.Timer - nick Name - quitTimer *time.Timer - realname Text - registered bool - server *Server - socket *Socket - username Name - theaterChannels []*Channel + atime time.Time + authorized bool + awayMessage Text + capabilities CapabilitySet + capState CapState + channels ChannelSet + commands chan Command + ctime time.Time + flags map[UserMode]bool + hasQuit bool + hops uint + hostname Name + idleTimer *time.Timer + loginTimer *time.Timer + nick Name + quitTimer *time.Timer + realname Text + registered bool + server *Server + socket *Socket + username Name } func NewClient(server *Server, conn net.Conn) *Client { @@ -260,10 +259,6 @@ func (client *Client) Quit(message Text) { return } - for _, channel := range client.theaterChannels { - delete(channel.flags, Theater) - } - client.Reply(RplError("connection closed")) client.hasQuit = true client.server.whoWas.Append(client) diff --git a/irc/commands.go b/irc/commands.go index c9a4550c..22153041 100644 --- a/irc/commands.go +++ b/irc/commands.go @@ -966,7 +966,7 @@ func NewTheaterCommand(args []string) (Command, error) { return &TheaterActionCommand{ channel: NewName(args[1]), asNick: NewName(args[2]), - action: NewText(args[3]), + action: NewCTCPText(args[3]), }, nil } else { return nil, ErrParseCommand diff --git a/irc/modes.go b/irc/modes.go index 44dd1607..717e9c32 100644 --- a/irc/modes.go +++ b/irc/modes.go @@ -83,7 +83,7 @@ const ( Quiet ChannelMode = 'q' // flag ReOp ChannelMode = 'r' // flag Secret ChannelMode = 's' // flag, deprecated - Theater ChannelMode = 'T' // flag arg, nonstandard + Theater ChannelMode = 'T' // flag, nonstandard UserLimit ChannelMode = 'l' // flag arg Voice ChannelMode = 'v' // arg ) diff --git a/irc/reply.go b/irc/reply.go index 040aedbf..2c11bea2 100644 --- a/irc/reply.go +++ b/irc/reply.go @@ -99,6 +99,10 @@ func RplPrivMsg(source Identifiable, target Identifiable, message Text) string { return NewStringReply(source, PRIVMSG, "%s :%s", target.Nick(), message) } +func RplCTCPAction(source Identifiable, target Identifiable, action CTCPText) string { + return RplPrivMsg(source, target, NewText(fmt.Sprintf("\x01ACTION %s\x01", action))) +} + func RplNotice(source Identifiable, target Identifiable, message Text) string { return NewStringReply(source, NOTICE, "%s :%s", target.Nick(), message) } diff --git a/irc/strings.go b/irc/strings.go index dfd01fcb..98304b1b 100644 --- a/irc/strings.go +++ b/irc/strings.go @@ -64,3 +64,12 @@ func NewText(str string) Text { func (text Text) String() string { return string(text) } + +// CTCPText is text suitably escaped for CTCP. +type CTCPText string + +var ctcpEscaper = strings.NewReplacer("\x00", "\x200", "\n", "\x20n", "\r", "\x20r") + +func NewCTCPText(str string) CTCPText { + return CTCPText(ctcpEscaper.Replace(str)) +} diff --git a/irc/theater.go b/irc/theater.go index 43f1d8d0..36525048 100644 --- a/irc/theater.go +++ b/irc/theater.go @@ -51,11 +51,12 @@ func (m *TheaterIdentifyCommand) HandleServer(s *Server) { return } - if channel.theaterUser == nil { - client.theaterChannels = append(client.theaterChannels, channel) - channel.flags[Theater] = true - channel.theaterUser = client + if channel.members.AnyHasMode(Theater) { + client.Reply(RplNotice(s, client, "someone else is +T in this channel")) + return } + + channel.members[client][Theater] = true } type TheaterPrivMsgCommand struct { @@ -71,6 +72,7 @@ func (cmd *TheaterPrivMsgCommand) String() string { } func (m *TheaterPrivMsgCommand) HandleServer(s *Server) { client := m.Client() + if !m.channel.IsChannel() { client.ErrNoSuchChannel(m.channel) return @@ -82,10 +84,13 @@ func (m *TheaterPrivMsgCommand) HandleServer(s *Server) { return } - if channel.theaterUser == client { - for member := range channel.members { - member.Reply(RplPrivMsg(TheaterClient(m.asNick), channel, m.message)) - } + if !channel.members.HasMode(client, Theater) { + client.Reply(RplNotice(s, client, "you are not +T")) + return + } + + for member := range channel.members { + member.Reply(RplPrivMsg(TheaterClient(m.asNick), channel, m.message)) } } @@ -93,7 +98,7 @@ type TheaterActionCommand struct { BaseCommand channel Name asNick Name - action Text + action CTCPText } func (cmd *TheaterActionCommand) String() string { @@ -102,7 +107,8 @@ func (cmd *TheaterActionCommand) String() string { func (m *TheaterActionCommand) HandleServer(s *Server) { client := m.Client() - if m.channel.IsChannel() { + + if !m.channel.IsChannel() { client.ErrNoSuchChannel(m.channel) return } @@ -113,9 +119,12 @@ func (m *TheaterActionCommand) HandleServer(s *Server) { return } - if channel.theaterUser == client { - for member := range channel.members { - member.Reply(RplPrivMsg(TheaterClient(m.asNick), channel, NewText(fmt.Sprintf("\001ACTION %s\001", m.action)))) - } + if !channel.members.HasMode(client, Theater) { + client.Reply(RplNotice(s, client, "you are not +T")) + return + } + + for member := range channel.members { + member.Reply(RplCTCPAction(TheaterClient(m.asNick), channel, m.action)) } } diff --git a/irc/types.go b/irc/types.go index 7c8a5a2f..dabe4c2b 100644 --- a/irc/types.go +++ b/irc/types.go @@ -83,6 +83,15 @@ func (members MemberSet) HasMode(member *Client, mode ChannelMode) bool { return modes[mode] } +func (members MemberSet) AnyHasMode(mode ChannelMode) bool { + for _, modes := range members { + if modes[mode] { + return true + } + } + return false +} + type ChannelSet map[*Channel]bool func (channels ChannelSet) Add(channel *Channel) {