From 504659abb52d59fe217fbabf9cc688a72aa69319 Mon Sep 17 00:00:00 2001 From: Alex Jaspersen Date: Sat, 19 Sep 2020 11:01:58 -0700 Subject: [PATCH] Add +M (only registered/voice can speak) chanmode. Add chanmode preventing speech to error message. Fixes #1182. --- docs/MANUAL.md | 12 ++++++++++++ irc/channel.go | 19 +++++++++++-------- irc/config.go | 2 +- irc/help.go | 1 + irc/modes.go | 2 +- irc/modes/modes.go | 11 ++++++----- irc/roleplay.go | 4 ++-- 7 files changed, 34 insertions(+), 17 deletions(-) diff --git a/docs/MANUAL.md b/docs/MANUAL.md index 422a8ddc..c2c1b3a2 100644 --- a/docs/MANUAL.md +++ b/docs/MANUAL.md @@ -654,6 +654,18 @@ To unset this mode: /MODE #test -R +### +M - Only Registered Users Can Speak + +If this mode is set, only users that have logged into an account will be able to speak on the channel. If this is set and a regular, un-logged-in user tries to speak, they will be rejected. Users who have been voiced (+v) are excepted from this restriction. + +To set this mode: + + /MODE #test +M + +To unset this mode: + + /MODE #test -M + ### +s - Secret If this mode is set, it means that your channel should be marked as 'secret'. Your channel won't show up in `/LIST` or `/WHOIS`, and non-members won't be able to see its members with `/NAMES` or `/WHO`. diff --git a/irc/channel.go b/irc/channel.go index 18c51d30..7153277a 100644 --- a/irc/channel.go +++ b/irc/channel.go @@ -1188,22 +1188,25 @@ func (channel *Channel) SetTopic(client *Client, topic string, rb *ResponseBuffe channel.MarkDirty(IncludeTopic) } -// CanSpeak returns true if the client can speak on this channel. -func (channel *Channel) CanSpeak(client *Client) bool { +// CanSpeak returns true if the client can speak on this channel, otherwise it returns false along with the channel mode preventing the client from speaking. +func (channel *Channel) CanSpeak(client *Client) (bool, modes.Mode) { channel.stateMutex.RLock() defer channel.stateMutex.RUnlock() _, hasClient := channel.members[client] if channel.flags.HasMode(modes.NoOutside) && !hasClient { - return false + return false, modes.NoOutside } if channel.flags.HasMode(modes.Moderated) && !channel.ClientIsAtLeast(client, modes.Voice) { - return false + return false, modes.Moderated } if channel.flags.HasMode(modes.RegisteredOnly) && client.Account() == "" { - return false + return false, modes.RegisteredOnly } - return true + if channel.flags.HasMode(modes.RegisteredOnlySpeak) && client.Account() == "" && !channel.ClientIsAtLeast(client, modes.Voice) { + return false, modes.RegisteredOnlySpeak + } + return true, modes.Mode('?') } func msgCommandToHistType(command string) (history.ItemType, error) { @@ -1225,9 +1228,9 @@ func (channel *Channel) SendSplitMessage(command string, minPrefixMode modes.Mod return } - if !channel.CanSpeak(client) { + if canSpeak, mode := channel.CanSpeak(client); !canSpeak { if histType != history.Notice { - rb.Add(nil, client.server.name, ERR_CANNOTSENDTOCHAN, client.Nick(), channel.Name(), client.t("Cannot send to channel")) + rb.Add(nil, client.server.name, ERR_CANNOTSENDTOCHAN, client.Nick(), channel.Name(), fmt.Sprintf(client.t("Cannot send to channel (+%s)"), mode)) } return } diff --git a/irc/config.go b/irc/config.go index 12e47ce0..f973f8c1 100644 --- a/irc/config.go +++ b/irc/config.go @@ -1250,7 +1250,7 @@ func (config *Config) generateISupport() (err error) { isupport.Add("BOT", "B") isupport.Add("CASEMAPPING", "ascii") isupport.Add("CHANLIMIT", fmt.Sprintf("%s:%d", chanTypes, config.Channels.MaxChannelsPerClient)) - isupport.Add("CHANMODES", strings.Join([]string{modes.Modes{modes.BanMask, modes.ExceptMask, modes.InviteMask}.String(), modes.Modes{modes.Key}.String(), modes.Modes{modes.UserLimit}.String(), modes.Modes{modes.InviteOnly, modes.Moderated, modes.NoOutside, modes.OpOnlyTopic, modes.ChanRoleplaying, modes.Secret, modes.NoCTCP, modes.RegisteredOnly}.String()}, ",")) + isupport.Add("CHANMODES", strings.Join([]string{modes.Modes{modes.BanMask, modes.ExceptMask, modes.InviteMask}.String(), modes.Modes{modes.Key}.String(), modes.Modes{modes.UserLimit}.String(), modes.Modes{modes.InviteOnly, modes.Moderated, modes.NoOutside, modes.OpOnlyTopic, modes.ChanRoleplaying, modes.Secret, modes.NoCTCP, modes.RegisteredOnly, modes.RegisteredOnlySpeak}.String()}, ",")) if config.History.Enabled && config.History.ChathistoryMax > 0 { isupport.Add("draft/CHATHISTORY", strconv.Itoa(config.History.ChathistoryMax)) } diff --git a/irc/help.go b/irc/help.go index 3105dbd9..62b7d81f 100644 --- a/irc/help.go +++ b/irc/help.go @@ -49,6 +49,7 @@ Oragono supports the following channel modes: +n | No-outside-messages mode, only users that are on the channel can send | messages to it. +R | Only registered users can join the channel. + +M | Only registered or voiced users can speak in the channel. +s | Secret mode, channel won't show up in /LIST or whois replies. +t | Only channel opers can modify the topic. +E | Roleplaying commands are enabled in the channel. diff --git a/irc/modes.go b/irc/modes.go index 86ab0cd6..17ad2b6e 100644 --- a/irc/modes.go +++ b/irc/modes.go @@ -271,7 +271,7 @@ func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, c applied = append(applied, change) } - case modes.InviteOnly, modes.Moderated, modes.NoOutside, modes.OpOnlyTopic, modes.RegisteredOnly, modes.Secret, modes.ChanRoleplaying, modes.NoCTCP: + case modes.InviteOnly, modes.Moderated, modes.NoOutside, modes.OpOnlyTopic, modes.RegisteredOnly, modes.Secret, modes.ChanRoleplaying, modes.NoCTCP, modes.RegisteredOnlySpeak: if change.Op == modes.List { continue } diff --git a/irc/modes/modes.go b/irc/modes/modes.go index 42e2ef42..8751ca1f 100644 --- a/irc/modes/modes.go +++ b/irc/modes/modes.go @@ -21,8 +21,8 @@ var ( // SupportedChannelModes are the channel modes that we support. SupportedChannelModes = Modes{ BanMask, ChanRoleplaying, ExceptMask, InviteMask, InviteOnly, Key, - Moderated, NoOutside, OpOnlyTopic, RegisteredOnly, Secret, UserLimit, - NoCTCP, + Moderated, NoOutside, OpOnlyTopic, RegisteredOnly, RegisteredOnlySpeak, + Secret, UserLimit, NoCTCP, } ) @@ -122,9 +122,10 @@ const ( NoOutside Mode = 'n' // flag OpOnlyTopic Mode = 't' // flag // RegisteredOnly mode is reused here from umode definition - Secret Mode = 's' // flag - UserLimit Mode = 'l' // flag arg - NoCTCP Mode = 'C' // flag + RegisteredOnlySpeak Mode = 'M' // flag + Secret Mode = 's' // flag + UserLimit Mode = 'l' // flag arg + NoCTCP Mode = 'C' // flag ) var ( diff --git a/irc/roleplay.go b/irc/roleplay.go index 14f9dfe4..ab036e38 100644 --- a/irc/roleplay.go +++ b/irc/roleplay.go @@ -74,8 +74,8 @@ func sendRoleplayMessage(server *Server, client *Client, source string, targetSt } targetString = channel.Name() - if !channel.CanSpeak(client) { - rb.Add(nil, client.server.name, ERR_CANNOTSENDTOCHAN, targetString, client.t("Cannot send to channel")) + if canSpeak, mode := channel.CanSpeak(client); !canSpeak { + rb.Add(nil, client.server.name, ERR_CANNOTSENDTOCHAN, targetString, fmt.Sprintf(client.t("Cannot send to channel (+%s)"), mode)) return }