privileges: Add founder/admin/halfop (qah), remove owner (O)

channel owner is from 2812 and unused in the real world
This commit is contained in:
Daniel Oaks 2016-04-14 09:35:36 +10:00
parent 5ee0f1c205
commit 3972fc49ed
5 changed files with 117 additions and 64 deletions

View File

@ -49,25 +49,33 @@ func (channel *Channel) ClientIsOperator(client *Client) bool {
return client.flags[Operator] || channel.members.HasMode(client, ChannelOperator) return client.flags[Operator] || channel.members.HasMode(client, ChannelOperator)
} }
// Prefixes returns a list of prefixes for the given set of channel modes.
func (modes ChannelModeSet) Prefixes(isMultiPrefix bool) string {
var prefixes string
// add prefixes in order from highest to lowest privs
for _, mode := range ChannelPrivModes {
if modes[mode] {
prefixes += ChannelModePrefixes[mode]
}
}
if modes[Voice] {
prefixes += ChannelModePrefixes[Voice]
}
if !isMultiPrefix && len(prefixes) > 1 {
prefixes = string(prefixes[0])
}
return prefixes
}
func (channel *Channel) Nicks(target *Client) []string { func (channel *Channel) Nicks(target *Client) []string {
isMultiPrefix := (target != nil) && target.capabilities[MultiPrefix] isMultiPrefix := (target != nil) && target.capabilities[MultiPrefix]
nicks := make([]string, len(channel.members)) nicks := make([]string, len(channel.members))
i := 0 i := 0
for client, modes := range channel.members { for client, modes := range channel.members {
if isMultiPrefix { nicks[i] += modes.Prefixes(isMultiPrefix)
if modes[ChannelOperator] {
nicks[i] += "@"
}
if modes[Voice] {
nicks[i] += "+"
}
} else {
if modes[ChannelOperator] {
nicks[i] += "@"
} else if modes[Voice] {
nicks[i] += "+"
}
}
nicks[i] += client.Nick().String() nicks[i] += client.Nick().String()
i += 1 i += 1
} }
@ -160,7 +168,7 @@ func (channel *Channel) Join(client *Client, key Text) {
client.channels.Add(channel) client.channels.Add(channel)
channel.members.Add(client) channel.members.Add(client)
if !channel.flags[Persistent] && (len(channel.members) == 1) { if !channel.flags[Persistent] && (len(channel.members) == 1) {
channel.members[client][ChannelCreator] = true channel.members[client][ChannelFounder] = true
channel.members[client][ChannelOperator] = true channel.members[client][ChannelOperator] = true
} }
@ -400,9 +408,38 @@ func (channel *Channel) applyMode(client *Client, change *ChannelModeChange) boo
channel.userLimit = limit channel.userLimit = limit
return true return true
case ChannelOperator, Voice: case ChannelFounder, ChannelAdmin, ChannelOperator, Halfop, Voice:
return channel.applyModeMember(client, change.mode, change.op, var hasPrivs bool
NewName(change.arg))
// make sure client has privs to edit the given prefix
for _, mode := range ChannelPrivModes {
if channel.members[client][mode] {
hasPrivs = true
// Admins can't give other people Admin or remove it from others,
// standard for that channel mode, we worry about this later
if mode == ChannelAdmin && change.mode == ChannelAdmin {
hasPrivs = false
}
break
} else if mode == change.mode {
break
}
}
name := NewName(change.arg)
if !hasPrivs {
if change.op == Remove && name.ToLower() == client.nick.ToLower() {
// success!
} else {
client.ErrChanOPrivIsNeeded(channel)
return false
}
}
return channel.applyModeMember(client, change.mode, change.op, name)
default: default:
client.ErrUnknownMode(change.mode, channel) client.ErrUnknownMode(change.mode, channel)

View File

@ -545,8 +545,9 @@ func ParseChannelModeCommand(channel Name, args []string) (Command, error) {
op: op, op: op,
} }
switch change.mode { switch change.mode {
// TODO(dan): separate this into the type A/B/C/D args and use those lists here
case Key, BanMask, ExceptMask, InviteMask, UserLimit, case Key, BanMask, ExceptMask, InviteMask, UserLimit,
ChannelOperator, ChannelCreator, Voice: ChannelOperator, ChannelFounder, ChannelAdmin, Halfop, Voice:
if len(args) > skipArgs { if len(args) > skipArgs {
change.arg = args[skipArgs] change.arg = args[skipArgs]
skipArgs += 1 skipArgs += 1

View File

@ -67,25 +67,26 @@ var (
) )
const ( const (
Anonymous ChannelMode = 'a' // flag ChannelFounder ChannelMode = 'q' // arg
BanMask ChannelMode = 'b' // arg ChannelAdmin ChannelMode = 'a' // arg
ChannelCreator ChannelMode = 'O' // flag
ChannelOperator ChannelMode = 'o' // arg ChannelOperator ChannelMode = 'o' // arg
ExceptMask ChannelMode = 'e' // arg Halfop ChannelMode = 'h' // arg
InviteMask ChannelMode = 'I' // arg
InviteOnly ChannelMode = 'i' // flag
Key ChannelMode = 'k' // flag arg
Moderated ChannelMode = 'm' // flag
NoOutside ChannelMode = 'n' // flag
OpOnlyTopic ChannelMode = 't' // flag
Persistent ChannelMode = 'P' // flag
Private ChannelMode = 'p' // flag
Quiet ChannelMode = 'q' // flag
ReOp ChannelMode = 'r' // flag
Secret ChannelMode = 's' // flag, deprecated
Theater ChannelMode = 'T' // flag, nonstandard
UserLimit ChannelMode = 'l' // flag arg
Voice ChannelMode = 'v' // arg Voice ChannelMode = 'v' // arg
BanMask ChannelMode = 'b' // arg
ExceptMask ChannelMode = 'e' // arg
InviteMask ChannelMode = 'I' // arg
InviteOnly ChannelMode = 'i' // flag
Key ChannelMode = 'k' // flag arg
Moderated ChannelMode = 'm' // flag
NoOutside ChannelMode = 'n' // flag
OpOnlyTopic ChannelMode = 't' // flag
Persistent ChannelMode = 'P' // flag
Private ChannelMode = 'p' // flag
ReOp ChannelMode = 'r' // flag
Secret ChannelMode = 's' // flag, deprecated
Theater ChannelMode = 'T' // flag, nonstandard
UserLimit ChannelMode = 'l' // flag arg
) )
var ( var (
@ -93,6 +94,20 @@ var (
BanMask, ExceptMask, InviteMask, InviteOnly, Key, NoOutside, BanMask, ExceptMask, InviteMask, InviteOnly, Key, NoOutside,
OpOnlyTopic, Persistent, Private, Theater, UserLimit, OpOnlyTopic, Persistent, Private, Theater, UserLimit,
} }
// ChannelPrivModes holds the list of modes that are privileged, ie founder/op/halfop, in order.
// voice is not in this list because it cannot perform channel operator actions.
ChannelPrivModes = ChannelModes{
ChannelFounder, ChannelAdmin, ChannelOperator, Halfop,
}
ChannelModePrefixes = map[ChannelMode]string{
ChannelFounder: "~",
ChannelAdmin: "&",
ChannelOperator: "@",
Halfop: "%",
Voice: "+",
}
) )
// //

View File

@ -298,21 +298,8 @@ func (target *Client) RplWhoReply(channel *Channel, client *Client) {
} }
if channel != nil { if channel != nil {
flags += channel.members[client].Prefixes(target.capabilities[MultiPrefix])
channelName = channel.name.String() channelName = channel.name.String()
if target.capabilities[MultiPrefix] {
if channel.members[client][ChannelOperator] {
flags += "@"
}
if channel.members[client][Voice] {
flags += "+"
}
} else {
if channel.members[client][ChannelOperator] {
flags += "@"
} else if channel.members[client][Voice] {
flags += "+"
}
}
} }
target.NumericReply(RPL_WHOREPLY, target.NumericReply(RPL_WHOREPLY,
"%s %s %s %s %s %s :%d %s", channelName, client.username, client.hostname, "%s %s %s %s %s %s :%d %s", channelName, client.username, client.hostname,
@ -432,7 +419,7 @@ func (target *Client) RplNamReply(channel *Channel) {
} }
func (target *Client) RplWhoisChannels(client *Client) { func (target *Client) RplWhoisChannels(client *Client) {
target.MultilineReply(client.WhoisChannelsNames(), RPL_WHOISCHANNELS, target.MultilineReply(client.WhoisChannelsNames(target.capabilities[MultiPrefix]), RPL_WHOISCHANNELS,
"%s :%s", client.Nick()) "%s :%s", client.Nick())
} }

View File

@ -111,7 +111,7 @@ func NewServer(config *Config) *Server {
// server.isupport.Add("MODES", "") //TODO(dan): Support max modes? // server.isupport.Add("MODES", "") //TODO(dan): Support max modes?
server.isupport.Add("NETWORK", config.Network.Name) server.isupport.Add("NETWORK", config.Network.Name)
// server.isupport.Add("NICKLEN", "") //TODO(dan): Support nick length // server.isupport.Add("NICKLEN", "") //TODO(dan): Support nick length
server.isupport.Add("PREFIX", "(ov)@+") server.isupport.Add("PREFIX", "(qaohv)~&@%+")
// server.isupport.Add("STATUSMSG", "@+") //TODO(dan): Autogenerate based on PREFIXes, support STATUSMSG // server.isupport.Add("STATUSMSG", "@+") //TODO(dan): Autogenerate based on PREFIXes, support STATUSMSG
// server.isupport.Add("TARGMAX", "") //TODO(dan): Support this // server.isupport.Add("TARGMAX", "") //TODO(dan): Support this
// server.isupport.Add("TOPICLEN", "") //TODO(dan): Support topic length // server.isupport.Add("TOPICLEN", "") //TODO(dan): Support topic length
@ -504,20 +504,12 @@ func (msg *PrivMsgCommand) HandleServer(server *Server) {
} }
} }
func (client *Client) WhoisChannelsNames() []string { func (client *Client) WhoisChannelsNames(isMultiPrefix bool) []string {
chstrs := make([]string, len(client.channels)) chstrs := make([]string, len(client.channels))
index := 0 index := 0
//TODO(dan): handle secret (+s) channels here properly?
for channel := range client.channels { for channel := range client.channels {
switch { chstrs[index] = channel.members[client].Prefixes(isMultiPrefix) + channel.name.String()
case channel.members[client][ChannelOperator]:
chstrs[index] = "@" + channel.name.String()
case channel.members[client][Voice]:
chstrs[index] = "+" + channel.name.String()
default:
chstrs[index] = channel.name.String()
}
index += 1 index += 1
} }
return chstrs return chstrs
@ -664,7 +656,28 @@ func (msg *KickCommand) HandleServer(server *Server) {
continue continue
} }
channel.Kick(client, target, msg.Comment()) // make sure client has privs to kick the given user
var hasPrivs bool
for _, mode := range ChannelPrivModes {
if channel.members[client][mode] {
hasPrivs = true
// admins cannot kick other admins
if mode == ChannelAdmin && channel.members[target][ChannelAdmin] {
hasPrivs = false
}
break
} else if channel.members[target][mode] {
break
}
}
if hasPrivs {
channel.Kick(client, target, msg.Comment())
} else {
client.ErrChanOPrivIsNeeded(channel)
}
} }
} }