mirror of
https://github.com/ergochat/ergo.git
synced 2024-12-22 18:52:41 +01:00
modes: Continue overhauling
This commit is contained in:
parent
657ed644cb
commit
05074e966d
@ -18,8 +18,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Channel struct {
|
type Channel struct {
|
||||||
flags ChannelModeSet
|
flags ModeSet
|
||||||
lists map[ChannelMode]*UserMaskSet
|
lists map[Mode]*UserMaskSet
|
||||||
key string
|
key string
|
||||||
membersMutex sync.RWMutex
|
membersMutex sync.RWMutex
|
||||||
members MemberSet
|
members MemberSet
|
||||||
@ -43,8 +43,8 @@ func NewChannel(s *Server, name string, addDefaultModes bool) *Channel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
channel := &Channel{
|
channel := &Channel{
|
||||||
flags: make(ChannelModeSet),
|
flags: make(ModeSet),
|
||||||
lists: map[ChannelMode]*UserMaskSet{
|
lists: map[Mode]*UserMaskSet{
|
||||||
BanMask: NewUserMaskSet(),
|
BanMask: NewUserMaskSet(),
|
||||||
ExceptMask: NewUserMaskSet(),
|
ExceptMask: NewUserMaskSet(),
|
||||||
InviteMask: NewUserMaskSet(),
|
InviteMask: NewUserMaskSet(),
|
||||||
@ -110,14 +110,14 @@ func (channel *Channel) namesNoMutex(client *Client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ClientIsAtLeast returns whether the client has at least the given channel privilege.
|
// ClientIsAtLeast returns whether the client has at least the given channel privilege.
|
||||||
func (channel *Channel) ClientIsAtLeast(client *Client, permission ChannelMode) bool {
|
func (channel *Channel) ClientIsAtLeast(client *Client, permission Mode) bool {
|
||||||
channel.membersMutex.RLock()
|
channel.membersMutex.RLock()
|
||||||
defer channel.membersMutex.RUnlock()
|
defer channel.membersMutex.RUnlock()
|
||||||
|
|
||||||
return channel.clientIsAtLeastNoMutex(client, permission)
|
return channel.clientIsAtLeastNoMutex(client, permission)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (channel *Channel) clientIsAtLeastNoMutex(client *Client, permission ChannelMode) bool {
|
func (channel *Channel) clientIsAtLeastNoMutex(client *Client, permission Mode) bool {
|
||||||
// requires RLock()
|
// requires RLock()
|
||||||
|
|
||||||
// get voice, since it's not a part of ChannelPrivModes
|
// get voice, since it's not a part of ChannelPrivModes
|
||||||
@ -140,7 +140,7 @@ func (channel *Channel) clientIsAtLeastNoMutex(client *Client, permission Channe
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prefixes returns a list of prefixes for the given set of channel modes.
|
// Prefixes returns a list of prefixes for the given set of channel modes.
|
||||||
func (modes ChannelModeSet) Prefixes(isMultiPrefix bool) string {
|
func (modes ModeSet) Prefixes(isMultiPrefix bool) string {
|
||||||
var prefixes string
|
var prefixes string
|
||||||
|
|
||||||
// add prefixes in order from highest to lowest privs
|
// add prefixes in order from highest to lowest privs
|
||||||
@ -394,11 +394,11 @@ func (channel *Channel) CanSpeak(client *Client) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TagMsg sends a tag message to everyone in this channel who can accept them.
|
// TagMsg sends a tag message to everyone in this channel who can accept them.
|
||||||
func (channel *Channel) TagMsg(msgid string, minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client) {
|
func (channel *Channel) TagMsg(msgid string, minPrefix *Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client) {
|
||||||
channel.sendMessage(msgid, "TAGMSG", []Capability{MessageTags}, minPrefix, clientOnlyTags, client, nil)
|
channel.sendMessage(msgid, "TAGMSG", []Capability{MessageTags}, minPrefix, clientOnlyTags, client, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (channel *Channel) sendMessage(msgid, cmd string, requiredCaps []Capability, minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message *string) {
|
func (channel *Channel) sendMessage(msgid, cmd string, requiredCaps []Capability, minPrefix *Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message *string) {
|
||||||
if !channel.CanSpeak(client) {
|
if !channel.CanSpeak(client) {
|
||||||
client.Send(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, "Cannot send to channel")
|
client.Send(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, "Cannot send to channel")
|
||||||
return
|
return
|
||||||
@ -408,7 +408,7 @@ func (channel *Channel) sendMessage(msgid, cmd string, requiredCaps []Capability
|
|||||||
defer channel.membersMutex.RUnlock()
|
defer channel.membersMutex.RUnlock()
|
||||||
|
|
||||||
// for STATUSMSG
|
// for STATUSMSG
|
||||||
var minPrefixMode ChannelMode
|
var minPrefixMode Mode
|
||||||
if minPrefix != nil {
|
if minPrefix != nil {
|
||||||
minPrefixMode = *minPrefix
|
minPrefixMode = *minPrefix
|
||||||
}
|
}
|
||||||
@ -445,16 +445,16 @@ func (channel *Channel) sendMessage(msgid, cmd string, requiredCaps []Capability
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SplitPrivMsg sends a private message to everyone in this channel.
|
// SplitPrivMsg sends a private message to everyone in this channel.
|
||||||
func (channel *Channel) SplitPrivMsg(msgid string, minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message SplitMessage) {
|
func (channel *Channel) SplitPrivMsg(msgid string, minPrefix *Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message SplitMessage) {
|
||||||
channel.sendSplitMessage(msgid, "PRIVMSG", minPrefix, clientOnlyTags, client, &message)
|
channel.sendSplitMessage(msgid, "PRIVMSG", minPrefix, clientOnlyTags, client, &message)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SplitNotice sends a private message to everyone in this channel.
|
// SplitNotice sends a private message to everyone in this channel.
|
||||||
func (channel *Channel) SplitNotice(msgid string, minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message SplitMessage) {
|
func (channel *Channel) SplitNotice(msgid string, minPrefix *Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message SplitMessage) {
|
||||||
channel.sendSplitMessage(msgid, "NOTICE", minPrefix, clientOnlyTags, client, &message)
|
channel.sendSplitMessage(msgid, "NOTICE", minPrefix, clientOnlyTags, client, &message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (channel *Channel) sendSplitMessage(msgid, cmd string, minPrefix *ChannelMode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message *SplitMessage) {
|
func (channel *Channel) sendSplitMessage(msgid, cmd string, minPrefix *Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message *SplitMessage) {
|
||||||
if !channel.CanSpeak(client) {
|
if !channel.CanSpeak(client) {
|
||||||
client.Send(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, "Cannot send to channel")
|
client.Send(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, "Cannot send to channel")
|
||||||
return
|
return
|
||||||
@ -464,7 +464,7 @@ func (channel *Channel) sendSplitMessage(msgid, cmd string, minPrefix *ChannelMo
|
|||||||
defer channel.membersMutex.RUnlock()
|
defer channel.membersMutex.RUnlock()
|
||||||
|
|
||||||
// for STATUSMSG
|
// for STATUSMSG
|
||||||
var minPrefixMode ChannelMode
|
var minPrefixMode Mode
|
||||||
if minPrefix != nil {
|
if minPrefix != nil {
|
||||||
minPrefixMode = *minPrefix
|
minPrefixMode = *minPrefix
|
||||||
}
|
}
|
||||||
@ -489,7 +489,7 @@ func (channel *Channel) sendSplitMessage(msgid, cmd string, minPrefix *ChannelMo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (channel *Channel) applyModeFlag(client *Client, mode ChannelMode,
|
func (channel *Channel) applyModeFlag(client *Client, mode Mode,
|
||||||
op ModeOp) bool {
|
op ModeOp) bool {
|
||||||
if !channel.ClientIsAtLeast(client, ChannelOperator) {
|
if !channel.ClientIsAtLeast(client, ChannelOperator) {
|
||||||
client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, "You're not a channel operator")
|
client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, "You're not a channel operator")
|
||||||
@ -514,8 +514,8 @@ func (channel *Channel) applyModeFlag(client *Client, mode ChannelMode,
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (channel *Channel) applyModeMemberNoMutex(client *Client, mode ChannelMode,
|
func (channel *Channel) applyModeMemberNoMutex(client *Client, mode Mode,
|
||||||
op ModeOp, nick string) *ChannelModeChange {
|
op ModeOp, nick string) *ModeChange {
|
||||||
// requires Lock()
|
// requires Lock()
|
||||||
|
|
||||||
if nick == "" {
|
if nick == "" {
|
||||||
@ -542,7 +542,7 @@ func (channel *Channel) applyModeMemberNoMutex(client *Client, mode ChannelMode,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
channel.members[target][mode] = true
|
channel.members[target][mode] = true
|
||||||
return &ChannelModeChange{
|
return &ModeChange{
|
||||||
op: Add,
|
op: Add,
|
||||||
mode: mode,
|
mode: mode,
|
||||||
arg: nick,
|
arg: nick,
|
||||||
@ -553,7 +553,7 @@ func (channel *Channel) applyModeMemberNoMutex(client *Client, mode ChannelMode,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
channel.members[target][mode] = false
|
channel.members[target][mode] = false
|
||||||
return &ChannelModeChange{
|
return &ModeChange{
|
||||||
op: Remove,
|
op: Remove,
|
||||||
mode: mode,
|
mode: mode,
|
||||||
arg: nick,
|
arg: nick,
|
||||||
@ -562,7 +562,7 @@ func (channel *Channel) applyModeMemberNoMutex(client *Client, mode ChannelMode,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (channel *Channel) ShowMaskList(client *Client, mode ChannelMode) {
|
func (channel *Channel) ShowMaskList(client *Client, mode Mode) {
|
||||||
//TODO(dan): WE NEED TO fiX this PROPERLY
|
//TODO(dan): WE NEED TO fiX this PROPERLY
|
||||||
log.Fatal("Implement ShowMaskList")
|
log.Fatal("Implement ShowMaskList")
|
||||||
/*
|
/*
|
||||||
@ -572,7 +572,7 @@ func (channel *Channel) ShowMaskList(client *Client, mode ChannelMode) {
|
|||||||
client.RplEndOfMaskList(mode, channel)*/
|
client.RplEndOfMaskList(mode, channel)*/
|
||||||
}
|
}
|
||||||
|
|
||||||
func (channel *Channel) applyModeMask(client *Client, mode ChannelMode, op ModeOp, mask string) bool {
|
func (channel *Channel) applyModeMask(client *Client, mode Mode, op ModeOp, mask string) bool {
|
||||||
list := channel.lists[mode]
|
list := channel.lists[mode]
|
||||||
if list == nil {
|
if list == nil {
|
||||||
// This should never happen, but better safe than panicky.
|
// This should never happen, but better safe than panicky.
|
||||||
|
@ -43,7 +43,7 @@ type Client struct {
|
|||||||
channels ChannelSet
|
channels ChannelSet
|
||||||
class *OperClass
|
class *OperClass
|
||||||
ctime time.Time
|
ctime time.Time
|
||||||
flags map[UserMode]bool
|
flags map[Mode]bool
|
||||||
isDestroyed bool
|
isDestroyed bool
|
||||||
isQuitting bool
|
isQuitting bool
|
||||||
hasQuit bool
|
hasQuit bool
|
||||||
@ -83,7 +83,7 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) *Client {
|
|||||||
capVersion: Cap301,
|
capVersion: Cap301,
|
||||||
channels: make(ChannelSet),
|
channels: make(ChannelSet),
|
||||||
ctime: now,
|
ctime: now,
|
||||||
flags: make(map[UserMode]bool),
|
flags: make(map[Mode]bool),
|
||||||
monitoring: make(map[string]bool),
|
monitoring: make(map[string]bool),
|
||||||
server: server,
|
server: server,
|
||||||
socket: &socket,
|
socket: &socket,
|
||||||
|
446
irc/modes.go
446
irc/modes.go
@ -178,8 +178,8 @@ func SplitChannelMembershipPrefixes(target string) (prefixes string, name string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetLowestChannelModePrefix returns the lowest channel prefix mode out of the given prefixes.
|
// GetLowestChannelModePrefix returns the lowest channel prefix mode out of the given prefixes.
|
||||||
func GetLowestChannelModePrefix(prefixes string) *ChannelMode {
|
func GetLowestChannelModePrefix(prefixes string) *Mode {
|
||||||
var lowest *ChannelMode
|
var lowest *Mode
|
||||||
|
|
||||||
if strings.Contains(prefixes, "+") {
|
if strings.Contains(prefixes, "+") {
|
||||||
lowest = &Voice
|
lowest = &Voice
|
||||||
@ -208,8 +208,8 @@ func modeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
return umodeHandler(server, client, msg)
|
return umodeHandler(server, client, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyModeChanges applies the given changes, and returns the applied changes.
|
// applyUserModeChanges applies the given changes, and returns the applied changes.
|
||||||
func (client *Client) applyModeChanges(ModeChanges) ModeChanges {
|
func (client *Client) applyUserModeChanges(changes ModeChanges) ModeChanges {
|
||||||
applied := make(ModeChanges, 0)
|
applied := make(ModeChanges, 0)
|
||||||
|
|
||||||
for _, change := range changes {
|
for _, change := range changes {
|
||||||
@ -289,13 +289,13 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
op = ModeOp(mode)
|
op = ModeOp(mode)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
changes = append(changes, &ModeChange{
|
changes = append(changes, ModeChange{
|
||||||
mode: Mode(mode),
|
mode: Mode(mode),
|
||||||
op: op,
|
op: op,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
applied := target.applyModeChanges(changes)
|
applied = target.applyUserModeChanges(changes)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(applied) > 0 {
|
if len(applied) > 0 {
|
||||||
@ -306,15 +306,219 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
//////
|
// ParseChannelModeChanges returns the valid changes, and the list of unknown chars.
|
||||||
//////
|
func ParseChannelModeChanges(params ...string) (ModeChanges, map[rune]bool) {
|
||||||
//////
|
changes := make(ModeChanges, 0)
|
||||||
//////
|
unknown := make(map[rune]bool)
|
||||||
//////
|
|
||||||
//////
|
if len(params) > 1 {
|
||||||
//////
|
modeArg := params[0]
|
||||||
//////
|
op := ModeOp(modeArg[0])
|
||||||
//////
|
if (op == Add) || (op == Remove) {
|
||||||
|
modeArg = modeArg[1:]
|
||||||
|
} else {
|
||||||
|
unknown[rune(modeArg[0])] = true
|
||||||
|
return changes, unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
skipArgs := 1
|
||||||
|
|
||||||
|
for _, mode := range modeArg {
|
||||||
|
if mode == '-' || mode == '+' {
|
||||||
|
op = ModeOp(mode)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
change := ModeChange{
|
||||||
|
mode: Mode(mode),
|
||||||
|
op: op,
|
||||||
|
}
|
||||||
|
|
||||||
|
// put arg into modechange if needed
|
||||||
|
switch Mode(mode) {
|
||||||
|
case BanMask, ExceptMask, InviteMask:
|
||||||
|
if len(params) > skipArgs {
|
||||||
|
change.arg = params[skipArgs]
|
||||||
|
skipArgs++
|
||||||
|
} else {
|
||||||
|
change.op = List
|
||||||
|
}
|
||||||
|
case ChannelFounder, ChannelAdmin, ChannelOperator, Halfop, Voice:
|
||||||
|
if len(params) > skipArgs {
|
||||||
|
change.arg = params[skipArgs]
|
||||||
|
skipArgs++
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case Key, UserLimit:
|
||||||
|
// don't require value when removing
|
||||||
|
if change.op == Add {
|
||||||
|
if len(params) > skipArgs {
|
||||||
|
change.arg = params[skipArgs]
|
||||||
|
skipArgs++
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
unknown[mode] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
changes = append(changes, change)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return changes, unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyChannelModeChanges applies a given set of mode changes.
|
||||||
|
func ApplyChannelModeChanges(channel *Channel, client *Client, isSamode bool, changes ModeChanges) ModeChanges {
|
||||||
|
// so we only output one warning for each list type when full
|
||||||
|
listFullWarned := make(map[Mode]bool)
|
||||||
|
|
||||||
|
clientIsOp := channel.clientIsAtLeastNoMutex(client, ChannelOperator)
|
||||||
|
var alreadySentPrivError bool
|
||||||
|
|
||||||
|
applied := make(ModeChanges, 0)
|
||||||
|
|
||||||
|
for _, change := range changes {
|
||||||
|
// chan priv modes are checked specially so ignore them
|
||||||
|
// means regular users can't view ban/except lists... but I'm not worried about that
|
||||||
|
if isSamode && ChannelModePrefixes[change.mode] == "" && !clientIsOp {
|
||||||
|
if !alreadySentPrivError {
|
||||||
|
alreadySentPrivError = true
|
||||||
|
client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, "You're not a channel operator")
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch change.mode {
|
||||||
|
case BanMask, ExceptMask, InviteMask:
|
||||||
|
mask := change.arg
|
||||||
|
list := channel.lists[change.mode]
|
||||||
|
if list == nil {
|
||||||
|
// This should never happen, but better safe than panicky.
|
||||||
|
client.Send(nil, client.server.name, ERR_UNKNOWNERROR, client.nick, "MODE", "Could not complete MODE command")
|
||||||
|
return changes
|
||||||
|
}
|
||||||
|
|
||||||
|
if (change.op == List) || (mask == "") {
|
||||||
|
channel.ShowMaskList(client, change.mode)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// confirm mask looks valid
|
||||||
|
mask, err := Casefold(mask)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch change.op {
|
||||||
|
case Add:
|
||||||
|
if len(list.masks) >= client.server.limits.ChanListModes {
|
||||||
|
if !listFullWarned[change.mode] {
|
||||||
|
client.Send(nil, client.server.name, ERR_BANLISTFULL, client.nick, channel.name, change.mode.String(), "Channel list is full")
|
||||||
|
listFullWarned[change.mode] = true
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
list.Add(mask)
|
||||||
|
applied = append(applied, change)
|
||||||
|
|
||||||
|
case Remove:
|
||||||
|
list.Remove(mask)
|
||||||
|
applied = append(applied, change)
|
||||||
|
}
|
||||||
|
|
||||||
|
case UserLimit:
|
||||||
|
switch change.op {
|
||||||
|
case Add:
|
||||||
|
val, err := strconv.ParseUint(change.arg, 10, 64)
|
||||||
|
if err == nil {
|
||||||
|
channel.userLimit = val
|
||||||
|
applied = append(applied, change)
|
||||||
|
}
|
||||||
|
|
||||||
|
case Remove:
|
||||||
|
channel.userLimit = 0
|
||||||
|
applied = append(applied, change)
|
||||||
|
}
|
||||||
|
|
||||||
|
case Key:
|
||||||
|
switch change.op {
|
||||||
|
case Add:
|
||||||
|
channel.key = change.arg
|
||||||
|
|
||||||
|
case Remove:
|
||||||
|
channel.key = ""
|
||||||
|
}
|
||||||
|
applied = append(applied, change)
|
||||||
|
|
||||||
|
case InviteOnly, Moderated, NoOutside, OpOnlyTopic, Secret, ChanRoleplaying:
|
||||||
|
switch change.op {
|
||||||
|
case Add:
|
||||||
|
if channel.flags[change.mode] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
channel.flags[change.mode] = true
|
||||||
|
applied = append(applied, change)
|
||||||
|
|
||||||
|
case Remove:
|
||||||
|
if !channel.flags[change.mode] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
delete(channel.flags, change.mode)
|
||||||
|
applied = append(applied, change)
|
||||||
|
}
|
||||||
|
|
||||||
|
case ChannelFounder, ChannelAdmin, ChannelOperator, Halfop, Voice:
|
||||||
|
// make sure client has privs to edit the given prefix
|
||||||
|
hasPrivs := isSamode
|
||||||
|
|
||||||
|
if !hasPrivs {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
casefoldedName, err := CasefoldName(change.arg)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasPrivs {
|
||||||
|
if change.op == Remove && casefoldedName == client.nickCasefolded {
|
||||||
|
// success!
|
||||||
|
} else {
|
||||||
|
if !alreadySentPrivError {
|
||||||
|
alreadySentPrivError = true
|
||||||
|
client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, "You're not a channel operator")
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
change := channel.applyModeMemberNoMutex(client, change.mode, change.op, change.arg)
|
||||||
|
if change != nil {
|
||||||
|
applied = append(applied, *change)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return applied
|
||||||
|
}
|
||||||
|
|
||||||
// MODE <target> [<modestring> [<mode arguments>...]]
|
// MODE <target> [<modestring> [<mode arguments>...]]
|
||||||
func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||||
@ -329,208 +533,24 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// assemble changes
|
// applied mode changes
|
||||||
//TODO(dan): split out assembling changes into func that returns changes, err
|
applied := make(ModeChanges, 0)
|
||||||
changes := make(ChannelModeChanges, 0)
|
|
||||||
applied := make(ChannelModeChanges, 0)
|
|
||||||
|
|
||||||
// TODO(dan): look at separating these into the type A/B/C/D args and using those lists here
|
if 1 < len(msg.Params) {
|
||||||
if len(msg.Params) > 1 {
|
// parse out real mode changes
|
||||||
modeArg := msg.Params[1]
|
params := msg.Params[1:]
|
||||||
op := ModeOp(modeArg[0])
|
changes, unknown := ParseChannelModeChanges(params...)
|
||||||
if (op == Add) || (op == Remove) {
|
|
||||||
modeArg = modeArg[1:]
|
// alert for unknown mode changes
|
||||||
} else {
|
for char := range unknown {
|
||||||
client.Send(nil, server.name, ERR_UNKNOWNMODE, client.nick, string(modeArg[0]), "is an unknown mode character to me")
|
client.Send(nil, server.name, ERR_UNKNOWNMODE, client.nick, string(char), "is an unknown mode character to me")
|
||||||
|
}
|
||||||
|
if len(unknown) == 1 && len(changes) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
skipArgs := 2
|
// apply mode changes
|
||||||
for _, mode := range modeArg {
|
applied = ApplyChannelModeChanges(channel, client, msg.Command == "SAMODE", changes)
|
||||||
if mode == '-' || mode == '+' {
|
|
||||||
op = ModeOp(mode)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
change := ChannelModeChange{
|
|
||||||
mode: ChannelMode(mode),
|
|
||||||
op: op,
|
|
||||||
}
|
|
||||||
|
|
||||||
// put arg into modechange if needed
|
|
||||||
switch ChannelMode(mode) {
|
|
||||||
case BanMask, ExceptMask, InviteMask:
|
|
||||||
if len(msg.Params) > skipArgs {
|
|
||||||
change.arg = msg.Params[skipArgs]
|
|
||||||
skipArgs++
|
|
||||||
} else {
|
|
||||||
change.op = List
|
|
||||||
}
|
|
||||||
case ChannelFounder, ChannelAdmin, ChannelOperator, Halfop, Voice:
|
|
||||||
if len(msg.Params) > skipArgs {
|
|
||||||
change.arg = msg.Params[skipArgs]
|
|
||||||
skipArgs++
|
|
||||||
} else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case Key, UserLimit:
|
|
||||||
// don't require value when removing
|
|
||||||
if change.op == Add {
|
|
||||||
if len(msg.Params) > skipArgs {
|
|
||||||
change.arg = msg.Params[skipArgs]
|
|
||||||
skipArgs++
|
|
||||||
} else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
changes = append(changes, &change)
|
|
||||||
}
|
|
||||||
|
|
||||||
// so we only output one warning for each list type when full
|
|
||||||
listFullWarned := make(map[ChannelMode]bool)
|
|
||||||
|
|
||||||
clientIsOp := channel.clientIsAtLeastNoMutex(client, ChannelOperator)
|
|
||||||
var alreadySentPrivError bool
|
|
||||||
|
|
||||||
for _, change := range changes {
|
|
||||||
// chan priv modes are checked specially so ignore them
|
|
||||||
// means regular users can't view ban/except lists... but I'm not worried about that
|
|
||||||
if msg.Command != "SAMODE" && ChannelModePrefixes[change.mode] == "" && !clientIsOp {
|
|
||||||
if !alreadySentPrivError {
|
|
||||||
alreadySentPrivError = true
|
|
||||||
client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, "You're not a channel operator")
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch change.mode {
|
|
||||||
case BanMask, ExceptMask, InviteMask:
|
|
||||||
mask := change.arg
|
|
||||||
list := channel.lists[change.mode]
|
|
||||||
if list == nil {
|
|
||||||
// This should never happen, but better safe than panicky.
|
|
||||||
client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, "MODE", "Could not complete MODE command")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (change.op == List) || (mask == "") {
|
|
||||||
channel.ShowMaskList(client, change.mode)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// confirm mask looks valid
|
|
||||||
mask, err = Casefold(mask)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch change.op {
|
|
||||||
case Add:
|
|
||||||
if len(list.masks) >= server.limits.ChanListModes {
|
|
||||||
if !listFullWarned[change.mode] {
|
|
||||||
client.Send(nil, server.name, ERR_BANLISTFULL, client.nick, channel.name, change.mode.String(), "Channel list is full")
|
|
||||||
listFullWarned[change.mode] = true
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
list.Add(mask)
|
|
||||||
applied = append(applied, change)
|
|
||||||
|
|
||||||
case Remove:
|
|
||||||
list.Remove(mask)
|
|
||||||
applied = append(applied, change)
|
|
||||||
}
|
|
||||||
|
|
||||||
case UserLimit:
|
|
||||||
switch change.op {
|
|
||||||
case Add:
|
|
||||||
val, err := strconv.ParseUint(change.arg, 10, 64)
|
|
||||||
if err == nil {
|
|
||||||
channel.userLimit = val
|
|
||||||
applied = append(applied, change)
|
|
||||||
}
|
|
||||||
|
|
||||||
case Remove:
|
|
||||||
channel.userLimit = 0
|
|
||||||
applied = append(applied, change)
|
|
||||||
}
|
|
||||||
|
|
||||||
case Key:
|
|
||||||
switch change.op {
|
|
||||||
case Add:
|
|
||||||
channel.key = change.arg
|
|
||||||
|
|
||||||
case Remove:
|
|
||||||
channel.key = ""
|
|
||||||
}
|
|
||||||
applied = append(applied, change)
|
|
||||||
|
|
||||||
case InviteOnly, Moderated, NoOutside, OpOnlyTopic, Secret, ChanRoleplaying:
|
|
||||||
switch change.op {
|
|
||||||
case Add:
|
|
||||||
if channel.flags[change.mode] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
channel.flags[change.mode] = true
|
|
||||||
applied = append(applied, change)
|
|
||||||
|
|
||||||
case Remove:
|
|
||||||
if !channel.flags[change.mode] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
delete(channel.flags, change.mode)
|
|
||||||
applied = append(applied, change)
|
|
||||||
}
|
|
||||||
|
|
||||||
case ChannelFounder, ChannelAdmin, ChannelOperator, Halfop, Voice:
|
|
||||||
// make sure client has privs to edit the given prefix
|
|
||||||
var hasPrivs bool
|
|
||||||
|
|
||||||
if msg.Command == "SAMODE" {
|
|
||||||
hasPrivs = true
|
|
||||||
} else {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
casefoldedName, err := CasefoldName(change.arg)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hasPrivs {
|
|
||||||
if change.op == Remove && casefoldedName == client.nickCasefolded {
|
|
||||||
// success!
|
|
||||||
} else {
|
|
||||||
if !alreadySentPrivError {
|
|
||||||
alreadySentPrivError = true
|
|
||||||
client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, "You're not a channel operator")
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
change := channel.applyModeMemberNoMutex(client, change.mode, change.op, change.arg)
|
|
||||||
if change != nil {
|
|
||||||
applied = append(applied, change)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(applied) > 0 {
|
if len(applied) > 0 {
|
||||||
|
@ -339,7 +339,7 @@ func (server *Server) setISupport() {
|
|||||||
server.isupport = NewISupportList()
|
server.isupport = NewISupportList()
|
||||||
server.isupport.Add("AWAYLEN", strconv.Itoa(server.limits.AwayLen))
|
server.isupport.Add("AWAYLEN", strconv.Itoa(server.limits.AwayLen))
|
||||||
server.isupport.Add("CASEMAPPING", casemappingName)
|
server.isupport.Add("CASEMAPPING", casemappingName)
|
||||||
server.isupport.Add("CHANMODES", strings.Join([]string{ChannelModes{BanMask, ExceptMask, InviteMask}.String(), "", ChannelModes{UserLimit, Key}.String(), ChannelModes{InviteOnly, Moderated, NoOutside, OpOnlyTopic, ChanRoleplaying, Secret}.String()}, ","))
|
server.isupport.Add("CHANMODES", strings.Join([]string{Modes{BanMask, ExceptMask, InviteMask}.String(), "", Modes{UserLimit, Key}.String(), Modes{InviteOnly, Moderated, NoOutside, OpOnlyTopic, ChanRoleplaying, Secret}.String()}, ","))
|
||||||
server.isupport.Add("CHANNELLEN", strconv.Itoa(server.limits.ChannelLen))
|
server.isupport.Add("CHANNELLEN", strconv.Itoa(server.limits.ChannelLen))
|
||||||
server.isupport.Add("CHANTYPES", "#")
|
server.isupport.Add("CHANTYPES", "#")
|
||||||
server.isupport.Add("EXCEPTS", "")
|
server.isupport.Add("EXCEPTS", "")
|
||||||
@ -376,7 +376,7 @@ func (server *Server) setISupport() {
|
|||||||
server.isupport.RegenerateCachedReply()
|
server.isupport.RegenerateCachedReply()
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadChannelList(channel *Channel, list string, maskMode ChannelMode) {
|
func loadChannelList(channel *Channel, list string, maskMode Mode) {
|
||||||
if list == "" {
|
if list == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1242,7 +1242,7 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
|
|
||||||
client.Send(nil, server.name, RPL_YOUREOPER, client.nick, "You are now an IRC operator")
|
client.Send(nil, server.name, RPL_YOUREOPER, client.nick, "You are now an IRC operator")
|
||||||
//TODO(dan): Should this be sent automagically as part of setting the flag/mode?
|
//TODO(dan): Should this be sent automagically as part of setting the flag/mode?
|
||||||
modech := ModeChanges{&ModeChange{
|
modech := ModeChanges{ModeChange{
|
||||||
mode: Operator,
|
mode: Operator,
|
||||||
op: Add,
|
op: Add,
|
||||||
}}
|
}}
|
||||||
@ -1521,7 +1521,7 @@ func awayHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
client.Send(nil, server.name, RPL_UNAWAY, client.nick, "You are no longer marked as being away")
|
client.Send(nil, server.name, RPL_UNAWAY, client.nick, "You are no longer marked as being away")
|
||||||
}
|
}
|
||||||
//TODO(dan): Should this be sent automagically as part of setting the flag/mode?
|
//TODO(dan): Should this be sent automagically as part of setting the flag/mode?
|
||||||
modech := ModeChanges{&ModeChange{
|
modech := ModeChanges{ModeChange{
|
||||||
mode: Away,
|
mode: Away,
|
||||||
op: op,
|
op: op,
|
||||||
}}
|
}}
|
||||||
|
12
irc/types.go
12
irc/types.go
@ -39,9 +39,9 @@ func (channels ChannelNameMap) Remove(channel *Channel) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChannelModeSet map[ChannelMode]bool
|
type ModeSet map[Mode]bool
|
||||||
|
|
||||||
func (set ChannelModeSet) String() string {
|
func (set ModeSet) String() string {
|
||||||
if len(set) == 0 {
|
if len(set) == 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -68,10 +68,10 @@ func (clients ClientSet) Has(client *Client) bool {
|
|||||||
return clients[client]
|
return clients[client]
|
||||||
}
|
}
|
||||||
|
|
||||||
type MemberSet map[*Client]ChannelModeSet
|
type MemberSet map[*Client]ModeSet
|
||||||
|
|
||||||
func (members MemberSet) Add(member *Client) {
|
func (members MemberSet) Add(member *Client) {
|
||||||
members[member] = make(ChannelModeSet)
|
members[member] = make(ModeSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (members MemberSet) Remove(member *Client) {
|
func (members MemberSet) Remove(member *Client) {
|
||||||
@ -83,7 +83,7 @@ func (members MemberSet) Has(member *Client) bool {
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (members MemberSet) HasMode(member *Client, mode ChannelMode) bool {
|
func (members MemberSet) HasMode(member *Client, mode Mode) bool {
|
||||||
modes, ok := members[member]
|
modes, ok := members[member]
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
@ -91,7 +91,7 @@ func (members MemberSet) HasMode(member *Client, mode ChannelMode) bool {
|
|||||||
return modes[mode]
|
return modes[mode]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (members MemberSet) AnyHasMode(mode ChannelMode) bool {
|
func (members MemberSet) AnyHasMode(mode Mode) bool {
|
||||||
for _, modes := range members {
|
for _, modes := range members {
|
||||||
if modes[mode] {
|
if modes[mode] {
|
||||||
return true
|
return true
|
||||||
|
Loading…
Reference in New Issue
Block a user