mirror of
https://github.com/ergochat/ergo.git
synced 2025-10-16 07:27:23 +02:00
Split modes into a subpackage (this is painful, but will force us to simplify and improve the API for interacting with modes)
This commit is contained in:
parent
9f71d9ce12
commit
3634d0601e
151
irc/channel.go
151
irc/channel.go
@ -15,6 +15,7 @@ import (
|
|||||||
|
|
||||||
"github.com/goshuirc/irc-go/ircmsg"
|
"github.com/goshuirc/irc-go/ircmsg"
|
||||||
"github.com/oragono/oragono/irc/caps"
|
"github.com/oragono/oragono/irc/caps"
|
||||||
|
"github.com/oragono/oragono/irc/modes"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -23,8 +24,8 @@ var (
|
|||||||
|
|
||||||
// Channel represents a channel that clients can join.
|
// Channel represents a channel that clients can join.
|
||||||
type Channel struct {
|
type Channel struct {
|
||||||
flags ModeSet
|
flags modes.ModeSet
|
||||||
lists map[Mode]*UserMaskSet
|
lists map[modes.Mode]*UserMaskSet
|
||||||
key string
|
key string
|
||||||
members MemberSet
|
members MemberSet
|
||||||
membersCache []*Client // allow iteration over channel members without holding the lock
|
membersCache []*Client // allow iteration over channel members without holding the lock
|
||||||
@ -53,11 +54,11 @@ func NewChannel(s *Server, name string, addDefaultModes bool, regInfo *Registere
|
|||||||
|
|
||||||
channel := &Channel{
|
channel := &Channel{
|
||||||
createdTime: time.Now(), // may be overwritten by applyRegInfo
|
createdTime: time.Now(), // may be overwritten by applyRegInfo
|
||||||
flags: make(ModeSet),
|
flags: make(modes.ModeSet),
|
||||||
lists: map[Mode]*UserMaskSet{
|
lists: map[modes.Mode]*UserMaskSet{
|
||||||
BanMask: NewUserMaskSet(),
|
modes.BanMask: NewUserMaskSet(),
|
||||||
ExceptMask: NewUserMaskSet(),
|
modes.ExceptMask: NewUserMaskSet(),
|
||||||
InviteMask: NewUserMaskSet(),
|
modes.InviteMask: NewUserMaskSet(),
|
||||||
},
|
},
|
||||||
members: make(MemberSet),
|
members: make(MemberSet),
|
||||||
name: name,
|
name: name,
|
||||||
@ -88,13 +89,13 @@ func (channel *Channel) applyRegInfo(chanReg *RegisteredChannel) {
|
|||||||
channel.name = chanReg.Name
|
channel.name = chanReg.Name
|
||||||
channel.createdTime = chanReg.RegisteredAt
|
channel.createdTime = chanReg.RegisteredAt
|
||||||
for _, mask := range chanReg.Banlist {
|
for _, mask := range chanReg.Banlist {
|
||||||
channel.lists[BanMask].Add(mask)
|
channel.lists[modes.BanMask].Add(mask)
|
||||||
}
|
}
|
||||||
for _, mask := range chanReg.Exceptlist {
|
for _, mask := range chanReg.Exceptlist {
|
||||||
channel.lists[ExceptMask].Add(mask)
|
channel.lists[modes.ExceptMask].Add(mask)
|
||||||
}
|
}
|
||||||
for _, mask := range chanReg.Invitelist {
|
for _, mask := range chanReg.Invitelist {
|
||||||
channel.lists[InviteMask].Add(mask)
|
channel.lists[modes.InviteMask].Add(mask)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,13 +112,13 @@ func (channel *Channel) ExportRegistration(includeLists bool) (info RegisteredCh
|
|||||||
info.RegisteredAt = channel.registeredTime
|
info.RegisteredAt = channel.registeredTime
|
||||||
|
|
||||||
if includeLists {
|
if includeLists {
|
||||||
for mask := range channel.lists[BanMask].masks {
|
for mask := range channel.lists[modes.BanMask].masks {
|
||||||
info.Banlist = append(info.Banlist, mask)
|
info.Banlist = append(info.Banlist, mask)
|
||||||
}
|
}
|
||||||
for mask := range channel.lists[ExceptMask].masks {
|
for mask := range channel.lists[modes.ExceptMask].masks {
|
||||||
info.Exceptlist = append(info.Exceptlist, mask)
|
info.Exceptlist = append(info.Exceptlist, mask)
|
||||||
}
|
}
|
||||||
for mask := range channel.lists[InviteMask].masks {
|
for mask := range channel.lists[modes.InviteMask].masks {
|
||||||
info.Invitelist = append(info.Invitelist, mask)
|
info.Invitelist = append(info.Invitelist, mask)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,7 +202,7 @@ func (channel *Channel) Names(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 Mode) bool {
|
func (channel *Channel) ClientIsAtLeast(client *Client, permission modes.Mode) bool {
|
||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
defer channel.stateMutex.RUnlock()
|
defer channel.stateMutex.RUnlock()
|
||||||
|
|
||||||
@ -211,7 +212,7 @@ func (channel *Channel) ClientIsAtLeast(client *Client, permission Mode) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check regular modes
|
// check regular modes
|
||||||
for _, mode := range ChannelPrivModes {
|
for _, mode := range modes.ChannelPrivModes {
|
||||||
if channel.members.HasMode(client, mode) {
|
if channel.members.HasMode(client, mode) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -224,27 +225,6 @@ func (channel *Channel) ClientIsAtLeast(client *Client, permission Mode) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prefixes returns a list of prefixes for the given set of channel modes.
|
|
||||||
func (modes ModeSet) 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) ClientPrefixes(client *Client, isMultiPrefix bool) string {
|
func (channel *Channel) ClientPrefixes(client *Client, isMultiPrefix bool) string {
|
||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
defer channel.stateMutex.RUnlock()
|
defer channel.stateMutex.RUnlock()
|
||||||
@ -263,11 +243,11 @@ func (channel *Channel) ClientHasPrivsOver(client *Client, target *Client) bool
|
|||||||
clientModes := channel.members[client]
|
clientModes := channel.members[client]
|
||||||
targetModes := channel.members[target]
|
targetModes := channel.members[target]
|
||||||
result := false
|
result := false
|
||||||
for _, mode := range ChannelPrivModes {
|
for _, mode := range modes.ChannelPrivModes {
|
||||||
if clientModes[mode] {
|
if clientModes[mode] {
|
||||||
result = true
|
result = true
|
||||||
// admins cannot kick other admins
|
// admins cannot kick other admins
|
||||||
if mode == ChannelAdmin && targetModes[ChannelAdmin] {
|
if mode == modes.ChannelAdmin && targetModes[modes.ChannelAdmin] {
|
||||||
result = false
|
result = false
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
@ -318,18 +298,18 @@ func (channel *Channel) hasClient(client *Client) bool {
|
|||||||
|
|
||||||
// <mode> <mode params>
|
// <mode> <mode params>
|
||||||
func (channel *Channel) modeStrings(client *Client) (result []string) {
|
func (channel *Channel) modeStrings(client *Client) (result []string) {
|
||||||
isMember := client.HasMode(Operator) || channel.hasClient(client)
|
isMember := client.HasMode(modes.Operator) || channel.hasClient(client)
|
||||||
showKey := isMember && (channel.key != "")
|
showKey := isMember && (channel.key != "")
|
||||||
showUserLimit := channel.userLimit > 0
|
showUserLimit := channel.userLimit > 0
|
||||||
|
|
||||||
modes := "+"
|
mods := "+"
|
||||||
|
|
||||||
// flags with args
|
// flags with args
|
||||||
if showKey {
|
if showKey {
|
||||||
modes += Key.String()
|
mods += modes.Key.String()
|
||||||
}
|
}
|
||||||
if showUserLimit {
|
if showUserLimit {
|
||||||
modes += UserLimit.String()
|
mods += modes.UserLimit.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
@ -337,10 +317,10 @@ func (channel *Channel) modeStrings(client *Client) (result []string) {
|
|||||||
|
|
||||||
// flags
|
// flags
|
||||||
for mode := range channel.flags {
|
for mode := range channel.flags {
|
||||||
modes += mode.String()
|
mods += mode.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
result = []string{modes}
|
result = []string{mods}
|
||||||
|
|
||||||
// args for flags with args: The order must match above to keep
|
// args for flags with args: The order must match above to keep
|
||||||
// positional arguments in place.
|
// positional arguments in place.
|
||||||
@ -390,15 +370,15 @@ func (channel *Channel) Join(client *Client, key string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isInvited := channel.lists[InviteMask].Match(client.nickMaskCasefolded)
|
isInvited := channel.lists[modes.InviteMask].Match(client.nickMaskCasefolded)
|
||||||
if channel.flags[InviteOnly] && !isInvited {
|
if channel.flags[modes.InviteOnly] && !isInvited {
|
||||||
client.Send(nil, client.server.name, ERR_INVITEONLYCHAN, channel.name, fmt.Sprintf(client.t("Cannot join channel (+%s)"), "i"))
|
client.Send(nil, client.server.name, ERR_INVITEONLYCHAN, channel.name, fmt.Sprintf(client.t("Cannot join channel (+%s)"), "i"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if channel.lists[BanMask].Match(client.nickMaskCasefolded) &&
|
if channel.lists[modes.BanMask].Match(client.nickMaskCasefolded) &&
|
||||||
!isInvited &&
|
!isInvited &&
|
||||||
!channel.lists[ExceptMask].Match(client.nickMaskCasefolded) {
|
!channel.lists[modes.ExceptMask].Match(client.nickMaskCasefolded) {
|
||||||
client.Send(nil, client.server.name, ERR_BANNEDFROMCHAN, channel.name, fmt.Sprintf(client.t("Cannot join channel (+%s)"), "b"))
|
client.Send(nil, client.server.name, ERR_BANNEDFROMCHAN, channel.name, fmt.Sprintf(client.t("Cannot join channel (+%s)"), "b"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -423,11 +403,11 @@ func (channel *Channel) Join(client *Client, key string) {
|
|||||||
|
|
||||||
// give channel mode if necessary
|
// give channel mode if necessary
|
||||||
newChannel := firstJoin && !channel.IsRegistered()
|
newChannel := firstJoin && !channel.IsRegistered()
|
||||||
var givenMode *Mode
|
var givenMode *modes.Mode
|
||||||
if client.AccountName() == channel.registeredFounder {
|
if client.AccountName() == channel.registeredFounder {
|
||||||
givenMode = &ChannelFounder
|
givenMode = &modes.ChannelFounder
|
||||||
} else if newChannel {
|
} else if newChannel {
|
||||||
givenMode = &ChannelOperator
|
givenMode = &modes.ChannelOperator
|
||||||
}
|
}
|
||||||
if givenMode != nil {
|
if givenMode != nil {
|
||||||
channel.stateMutex.Lock()
|
channel.stateMutex.Lock()
|
||||||
@ -492,12 +472,12 @@ func (channel *Channel) SendTopic(client *Client) {
|
|||||||
|
|
||||||
// SetTopic sets the topic of this channel, if the client is allowed to do so.
|
// SetTopic sets the topic of this channel, if the client is allowed to do so.
|
||||||
func (channel *Channel) SetTopic(client *Client, topic string) {
|
func (channel *Channel) SetTopic(client *Client, topic string) {
|
||||||
if !(client.flags[Operator] || channel.hasClient(client)) {
|
if !(client.flags[modes.Operator] || channel.hasClient(client)) {
|
||||||
client.Send(nil, client.server.name, ERR_NOTONCHANNEL, channel.name, client.t("You're not on that channel"))
|
client.Send(nil, client.server.name, ERR_NOTONCHANNEL, channel.name, client.t("You're not on that channel"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if channel.HasMode(OpOnlyTopic) && !channel.ClientIsAtLeast(client, ChannelOperator) {
|
if channel.HasMode(modes.OpOnlyTopic) && !channel.ClientIsAtLeast(client, modes.ChannelOperator) {
|
||||||
client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, client.t("You're not a channel operator"))
|
client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, client.t("You're not a channel operator"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -525,32 +505,32 @@ func (channel *Channel) CanSpeak(client *Client) bool {
|
|||||||
defer channel.stateMutex.RUnlock()
|
defer channel.stateMutex.RUnlock()
|
||||||
|
|
||||||
_, hasClient := channel.members[client]
|
_, hasClient := channel.members[client]
|
||||||
if channel.flags[NoOutside] && !hasClient {
|
if channel.flags[modes.NoOutside] && !hasClient {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if channel.flags[Moderated] && !channel.ClientIsAtLeast(client, Voice) {
|
if channel.flags[modes.Moderated] && !channel.ClientIsAtLeast(client, modes.Voice) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if channel.flags[RegisteredOnly] && client.account == &NoAccount {
|
if channel.flags[modes.RegisteredOnly] && client.account == &NoAccount {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 *Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client) {
|
func (channel *Channel) TagMsg(msgid string, minPrefix *modes.Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client) {
|
||||||
channel.sendMessage(msgid, "TAGMSG", []caps.Capability{caps.MessageTags}, minPrefix, clientOnlyTags, client, nil)
|
channel.sendMessage(msgid, "TAGMSG", []caps.Capability{caps.MessageTags}, minPrefix, clientOnlyTags, client, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendMessage sends a given message to everyone on this channel.
|
// sendMessage sends a given message to everyone on this channel.
|
||||||
func (channel *Channel) sendMessage(msgid, cmd string, requiredCaps []caps.Capability, minPrefix *Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message *string) {
|
func (channel *Channel) sendMessage(msgid, cmd string, requiredCaps []caps.Capability, minPrefix *modes.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, client.t("Cannot send to channel"))
|
client.Send(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// for STATUSMSG
|
// for STATUSMSG
|
||||||
var minPrefixMode Mode
|
var minPrefixMode modes.Mode
|
||||||
if minPrefix != nil {
|
if minPrefix != nil {
|
||||||
minPrefixMode = *minPrefix
|
minPrefixMode = *minPrefix
|
||||||
}
|
}
|
||||||
@ -587,23 +567,23 @@ func (channel *Channel) sendMessage(msgid, cmd string, requiredCaps []caps.Capab
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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 *Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message SplitMessage) {
|
func (channel *Channel) SplitPrivMsg(msgid string, minPrefix *modes.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 *Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message SplitMessage) {
|
func (channel *Channel) SplitNotice(msgid string, minPrefix *modes.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 *Mode, clientOnlyTags *map[string]ircmsg.TagValue, client *Client, message *SplitMessage) {
|
func (channel *Channel) sendSplitMessage(msgid, cmd string, minPrefix *modes.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, client.t("Cannot send to channel"))
|
client.Send(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// for STATUSMSG
|
// for STATUSMSG
|
||||||
var minPrefixMode Mode
|
var minPrefixMode modes.Mode
|
||||||
if minPrefix != nil {
|
if minPrefix != nil {
|
||||||
minPrefixMode = *minPrefix
|
minPrefixMode = *minPrefix
|
||||||
}
|
}
|
||||||
@ -628,8 +608,7 @@ func (channel *Channel) sendSplitMessage(msgid, cmd string, minPrefix *Mode, cli
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (channel *Channel) applyModeMemberNoMutex(client *Client, mode Mode,
|
func (channel *Channel) applyModeMemberNoMutex(client *Client, mode modes.Mode, op modes.ModeOp, nick string) *modes.ModeChange {
|
||||||
op ModeOp, nick string) *ModeChange {
|
|
||||||
if nick == "" {
|
if nick == "" {
|
||||||
//TODO(dan): shouldn't this be handled before it reaches this function?
|
//TODO(dan): shouldn't this be handled before it reaches this function?
|
||||||
client.Send(nil, client.server.name, ERR_NEEDMOREPARAMS, "MODE", client.t("Not enough parameters"))
|
client.Send(nil, client.server.name, ERR_NEEDMOREPARAMS, "MODE", client.t("Not enough parameters"))
|
||||||
@ -647,7 +626,7 @@ func (channel *Channel) applyModeMemberNoMutex(client *Client, mode Mode,
|
|||||||
modeset, exists := channel.members[target]
|
modeset, exists := channel.members[target]
|
||||||
var already bool
|
var already bool
|
||||||
if exists {
|
if exists {
|
||||||
enable := op == Add
|
enable := op == modes.Add
|
||||||
already = modeset[mode] == enable
|
already = modeset[mode] == enable
|
||||||
modeset[mode] = enable
|
modeset[mode] = enable
|
||||||
}
|
}
|
||||||
@ -659,25 +638,25 @@ func (channel *Channel) applyModeMemberNoMutex(client *Client, mode Mode,
|
|||||||
} else if already {
|
} else if already {
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
return &ModeChange{
|
return &modes.ModeChange{
|
||||||
op: op,
|
Op: op,
|
||||||
mode: mode,
|
Mode: mode,
|
||||||
arg: nick,
|
Arg: nick,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShowMaskList shows the given list to the client.
|
// ShowMaskList shows the given list to the client.
|
||||||
func (channel *Channel) ShowMaskList(client *Client, mode Mode) {
|
func (channel *Channel) ShowMaskList(client *Client, mode modes.Mode) {
|
||||||
// choose appropriate modes
|
// choose appropriate modes
|
||||||
var rpllist, rplendoflist string
|
var rpllist, rplendoflist string
|
||||||
if mode == BanMask {
|
if mode == modes.BanMask {
|
||||||
rpllist = RPL_BANLIST
|
rpllist = RPL_BANLIST
|
||||||
rplendoflist = RPL_ENDOFBANLIST
|
rplendoflist = RPL_ENDOFBANLIST
|
||||||
} else if mode == ExceptMask {
|
} else if mode == modes.ExceptMask {
|
||||||
rpllist = RPL_EXCEPTLIST
|
rpllist = RPL_EXCEPTLIST
|
||||||
rplendoflist = RPL_ENDOFEXCEPTLIST
|
rplendoflist = RPL_ENDOFEXCEPTLIST
|
||||||
} else if mode == InviteMask {
|
} else if mode == modes.InviteMask {
|
||||||
rpllist = RPL_INVITELIST
|
rpllist = RPL_INVITELIST
|
||||||
rplendoflist = RPL_ENDOFINVITELIST
|
rplendoflist = RPL_ENDOFINVITELIST
|
||||||
}
|
}
|
||||||
@ -693,28 +672,28 @@ func (channel *Channel) ShowMaskList(client *Client, mode Mode) {
|
|||||||
client.Send(nil, client.server.name, rplendoflist, nick, channel.name, client.t("End of list"))
|
client.Send(nil, client.server.name, rplendoflist, nick, channel.name, client.t("End of list"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (channel *Channel) applyModeMask(client *Client, mode Mode, op ModeOp, mask string) bool {
|
func (channel *Channel) applyModeMask(client *Client, mode modes.Mode, op modes.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.
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (op == List) || (mask == "") {
|
if (op == modes.List) || (mask == "") {
|
||||||
channel.ShowMaskList(client, mode)
|
channel.ShowMaskList(client, mode)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if !channel.ClientIsAtLeast(client, ChannelOperator) {
|
if !channel.ClientIsAtLeast(client, modes.ChannelOperator) {
|
||||||
client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, client.t("You're not a channel operator"))
|
client.Send(nil, client.server.name, ERR_CHANOPRIVSNEEDED, channel.name, client.t("You're not a channel operator"))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if op == Add {
|
if op == modes.Add {
|
||||||
return list.Add(mask)
|
return list.Add(mask)
|
||||||
}
|
}
|
||||||
|
|
||||||
if op == Remove {
|
if op == modes.Remove {
|
||||||
return list.Remove(mask)
|
return list.Remove(mask)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -732,11 +711,11 @@ func (channel *Channel) Quit(client *Client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (channel *Channel) Kick(client *Client, target *Client, comment string) {
|
func (channel *Channel) Kick(client *Client, target *Client, comment string) {
|
||||||
if !(client.flags[Operator] || channel.hasClient(client)) {
|
if !(client.flags[modes.Operator] || channel.hasClient(client)) {
|
||||||
client.Send(nil, client.server.name, ERR_NOTONCHANNEL, channel.name, client.t("You're not on that channel"))
|
client.Send(nil, client.server.name, ERR_NOTONCHANNEL, channel.name, client.t("You're not on that channel"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !channel.ClientIsAtLeast(client, ChannelOperator) {
|
if !channel.ClientIsAtLeast(client, modes.ChannelOperator) {
|
||||||
client.Send(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel"))
|
client.Send(nil, client.server.name, ERR_CANNOTSENDTOCHAN, channel.name, client.t("Cannot send to channel"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -765,7 +744,7 @@ func (channel *Channel) Kick(client *Client, target *Client, comment string) {
|
|||||||
|
|
||||||
// Invite invites the given client to the channel, if the inviter can do so.
|
// Invite invites the given client to the channel, if the inviter can do so.
|
||||||
func (channel *Channel) Invite(invitee *Client, inviter *Client) {
|
func (channel *Channel) Invite(invitee *Client, inviter *Client) {
|
||||||
if channel.flags[InviteOnly] && !channel.ClientIsAtLeast(inviter, ChannelOperator) {
|
if channel.flags[modes.InviteOnly] && !channel.ClientIsAtLeast(inviter, modes.ChannelOperator) {
|
||||||
inviter.Send(nil, inviter.server.name, ERR_CHANOPRIVSNEEDED, channel.name, inviter.t("You're not a channel operator"))
|
inviter.Send(nil, inviter.server.name, ERR_CHANOPRIVSNEEDED, channel.name, inviter.t("You're not a channel operator"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -776,15 +755,15 @@ func (channel *Channel) Invite(invitee *Client, inviter *Client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//TODO(dan): handle this more nicely, keep a list of last X invited channels on invitee rather than explicitly modifying the invite list?
|
//TODO(dan): handle this more nicely, keep a list of last X invited channels on invitee rather than explicitly modifying the invite list?
|
||||||
if channel.flags[InviteOnly] {
|
if channel.flags[modes.InviteOnly] {
|
||||||
nmc := invitee.NickCasefolded()
|
nmc := invitee.NickCasefolded()
|
||||||
channel.stateMutex.Lock()
|
channel.stateMutex.Lock()
|
||||||
channel.lists[InviteMask].Add(nmc)
|
channel.lists[modes.InviteMask].Add(nmc)
|
||||||
channel.stateMutex.Unlock()
|
channel.stateMutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, member := range channel.Members() {
|
for _, member := range channel.Members() {
|
||||||
if member.capabilities.Has(caps.InviteNotify) && member != inviter && member != invitee && channel.ClientIsAtLeast(member, Halfop) {
|
if member.capabilities.Has(caps.InviteNotify) && member != inviter && member != invitee && channel.ClientIsAtLeast(member, modes.Halfop) {
|
||||||
member.Send(nil, inviter.NickMaskString(), "INVITE", invitee.Nick(), channel.name)
|
member.Send(nil, inviter.NickMaskString(), "INVITE", invitee.Nick(), channel.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -792,7 +771,7 @@ func (channel *Channel) Invite(invitee *Client, inviter *Client) {
|
|||||||
//TODO(dan): should inviter.server.name here be inviter.nickMaskString ?
|
//TODO(dan): should inviter.server.name here be inviter.nickMaskString ?
|
||||||
inviter.Send(nil, inviter.server.name, RPL_INVITING, invitee.nick, channel.name)
|
inviter.Send(nil, inviter.server.name, RPL_INVITING, invitee.nick, channel.name)
|
||||||
invitee.Send(nil, inviter.nickMaskString, "INVITE", invitee.nick, channel.name)
|
invitee.Send(nil, inviter.nickMaskString, "INVITE", invitee.nick, channel.name)
|
||||||
if invitee.flags[Away] {
|
if invitee.flags[modes.Away] {
|
||||||
inviter.Send(nil, inviter.server.name, RPL_AWAY, invitee.nick, invitee.awayMessage)
|
inviter.Send(nil, inviter.server.name, RPL_AWAY, invitee.nick, invitee.awayMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/goshuirc/irc-go/ircfmt"
|
"github.com/goshuirc/irc-go/ircfmt"
|
||||||
|
"github.com/oragono/oragono/irc/modes"
|
||||||
"github.com/oragono/oragono/irc/sno"
|
"github.com/oragono/oragono/irc/sno"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -55,7 +56,7 @@ func (server *Server) chanservReceivePrivmsg(client *Client, message string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
channelInfo := server.channels.Get(channelKey)
|
channelInfo := server.channels.Get(channelKey)
|
||||||
if channelInfo == nil || !channelInfo.ClientIsAtLeast(client, ChannelOperator) {
|
if channelInfo == nil || !channelInfo.ClientIsAtLeast(client, modes.ChannelOperator) {
|
||||||
client.ChanServNotice(client.t("You must be an oper on the channel to register it"))
|
client.ChanServNotice(client.t("You must be an oper on the channel to register it"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -81,7 +82,7 @@ func (server *Server) chanservReceivePrivmsg(client *Client, message string) {
|
|||||||
server.snomasks.Send(sno.LocalChannels, fmt.Sprintf(ircfmt.Unescape("Channel registered $c[grey][$r%s$c[grey]] by $c[grey][$r%s$c[grey]]"), channelName, client.nickMaskString))
|
server.snomasks.Send(sno.LocalChannels, fmt.Sprintf(ircfmt.Unescape("Channel registered $c[grey][$r%s$c[grey]] by $c[grey][$r%s$c[grey]]"), channelName, client.nickMaskString))
|
||||||
|
|
||||||
// give them founder privs
|
// give them founder privs
|
||||||
change := channelInfo.applyModeMemberNoMutex(client, ChannelFounder, Add, client.NickCasefolded())
|
change := channelInfo.applyModeMemberNoMutex(client, modes.ChannelFounder, modes.Add, client.NickCasefolded())
|
||||||
if change != nil {
|
if change != nil {
|
||||||
//TODO(dan): we should change the name of String and make it return a slice here
|
//TODO(dan): we should change the name of String and make it return a slice here
|
||||||
//TODO(dan): unify this code with code in modes.go
|
//TODO(dan): unify this code with code in modes.go
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/goshuirc/irc-go/ircmsg"
|
"github.com/goshuirc/irc-go/ircmsg"
|
||||||
ident "github.com/oragono/go-ident"
|
ident "github.com/oragono/go-ident"
|
||||||
"github.com/oragono/oragono/irc/caps"
|
"github.com/oragono/oragono/irc/caps"
|
||||||
|
"github.com/oragono/oragono/irc/modes"
|
||||||
"github.com/oragono/oragono/irc/sno"
|
"github.com/oragono/oragono/irc/sno"
|
||||||
"github.com/oragono/oragono/irc/utils"
|
"github.com/oragono/oragono/irc/utils"
|
||||||
)
|
)
|
||||||
@ -50,7 +51,7 @@ type Client struct {
|
|||||||
class *OperClass
|
class *OperClass
|
||||||
ctime time.Time
|
ctime time.Time
|
||||||
exitedSnomaskSent bool
|
exitedSnomaskSent bool
|
||||||
flags map[Mode]bool
|
flags map[modes.Mode]bool
|
||||||
hasQuit bool
|
hasQuit bool
|
||||||
hops int
|
hops int
|
||||||
hostname string
|
hostname string
|
||||||
@ -95,7 +96,7 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) *Client {
|
|||||||
capVersion: caps.Cap301,
|
capVersion: caps.Cap301,
|
||||||
channels: make(ChannelSet),
|
channels: make(ChannelSet),
|
||||||
ctime: now,
|
ctime: now,
|
||||||
flags: make(map[Mode]bool),
|
flags: make(map[modes.Mode]bool),
|
||||||
server: server,
|
server: server,
|
||||||
socket: &socket,
|
socket: &socket,
|
||||||
account: &NoAccount,
|
account: &NoAccount,
|
||||||
@ -107,7 +108,7 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) *Client {
|
|||||||
|
|
||||||
client.recomputeMaxlens()
|
client.recomputeMaxlens()
|
||||||
if isTLS {
|
if isTLS {
|
||||||
client.flags[TLS] = true
|
client.flags[modes.TLS] = true
|
||||||
|
|
||||||
// error is not useful to us here anyways so we can ignore it
|
// error is not useful to us here anyways so we can ignore it
|
||||||
client.certfp, _ = client.socket.CertFP()
|
client.certfp, _ = client.socket.CertFP()
|
||||||
@ -348,7 +349,7 @@ func (client *Client) TryResume() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !oldClient.HasMode(TLS) || !client.HasMode(TLS) {
|
if !oldClient.HasMode(modes.TLS) || !client.HasMode(modes.TLS) {
|
||||||
client.Send(nil, server.name, ERR_CANNOT_RESUME, oldnick, client.t("Cannot resume connection, old and new clients must have TLS"))
|
client.Send(nil, server.name, ERR_CANNOT_RESUME, oldnick, client.t("Cannot resume connection, old and new clients must have TLS"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,10 @@
|
|||||||
|
|
||||||
package irc
|
package irc
|
||||||
|
|
||||||
import "github.com/goshuirc/irc-go/ircmsg"
|
import (
|
||||||
|
"github.com/goshuirc/irc-go/ircmsg"
|
||||||
|
"github.com/oragono/oragono/irc/modes"
|
||||||
|
)
|
||||||
|
|
||||||
// Command represents a command accepted from a client.
|
// Command represents a command accepted from a client.
|
||||||
type Command struct {
|
type Command struct {
|
||||||
@ -24,7 +27,7 @@ func (cmd *Command) Run(server *Server, client *Client, msg ircmsg.IrcMessage) b
|
|||||||
client.Send(nil, server.name, ERR_NOTREGISTERED, client.nick, client.t("You need to register before you can use that command"))
|
client.Send(nil, server.name, ERR_NOTREGISTERED, client.nick, client.t("You need to register before you can use that command"))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if cmd.oper && !client.flags[Operator] {
|
if cmd.oper && !client.flags[modes.Operator] {
|
||||||
client.Send(nil, server.name, ERR_NOPRIVILEGES, client.nick, client.t("Permission Denied - You're not an IRC operator"))
|
client.Send(nil, server.name, ERR_NOPRIVILEGES, client.nick, client.t("Permission Denied - You're not an IRC operator"))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"github.com/oragono/oragono/irc/modes"
|
||||||
"github.com/oragono/oragono/irc/passwd"
|
"github.com/oragono/oragono/irc/passwd"
|
||||||
"github.com/oragono/oragono/irc/utils"
|
"github.com/oragono/oragono/irc/utils"
|
||||||
)
|
)
|
||||||
@ -82,9 +83,9 @@ func (client *Client) ApplyProxiedIP(proxiedIP string, tls bool) (exiting bool)
|
|||||||
// set tls info
|
// set tls info
|
||||||
client.certfp = ""
|
client.certfp = ""
|
||||||
if tls {
|
if tls {
|
||||||
client.flags[TLS] = true
|
client.flags[modes.TLS] = true
|
||||||
} else {
|
} else {
|
||||||
delete(client.flags, TLS)
|
delete(client.flags, modes.TLS)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
@ -3,7 +3,10 @@
|
|||||||
|
|
||||||
package irc
|
package irc
|
||||||
|
|
||||||
import "github.com/oragono/oragono/irc/isupport"
|
import (
|
||||||
|
"github.com/oragono/oragono/irc/isupport"
|
||||||
|
"github.com/oragono/oragono/irc/modes"
|
||||||
|
)
|
||||||
|
|
||||||
func (server *Server) ISupport() *isupport.List {
|
func (server *Server) ISupport() *isupport.List {
|
||||||
server.configurableStateMutex.RLock()
|
server.configurableStateMutex.RLock()
|
||||||
@ -41,7 +44,7 @@ func (server *Server) WebIRCConfig() []webircConfig {
|
|||||||
return server.webirc
|
return server.webirc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) DefaultChannelModes() Modes {
|
func (server *Server) DefaultChannelModes() modes.Modes {
|
||||||
server.configurableStateMutex.RLock()
|
server.configurableStateMutex.RLock()
|
||||||
defer server.configurableStateMutex.RUnlock()
|
defer server.configurableStateMutex.RUnlock()
|
||||||
return server.defaultChannelModes
|
return server.defaultChannelModes
|
||||||
@ -107,7 +110,7 @@ func (client *Client) AccountName() string {
|
|||||||
return client.account.Name
|
return client.account.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *Client) HasMode(mode Mode) bool {
|
func (client *Client) HasMode(mode modes.Mode) bool {
|
||||||
client.stateMutex.RLock()
|
client.stateMutex.RLock()
|
||||||
defer client.stateMutex.RUnlock()
|
defer client.stateMutex.RUnlock()
|
||||||
return client.flags[mode]
|
return client.flags[mode]
|
||||||
@ -180,7 +183,7 @@ func (channel *Channel) setKey(key string) {
|
|||||||
channel.stateMutex.Unlock()
|
channel.stateMutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (channel *Channel) HasMode(mode Mode) bool {
|
func (channel *Channel) HasMode(mode modes.Mode) bool {
|
||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
defer channel.stateMutex.RUnlock()
|
defer channel.stateMutex.RUnlock()
|
||||||
return channel.flags[mode]
|
return channel.flags[mode]
|
||||||
@ -193,7 +196,7 @@ func (channel *Channel) Founder() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set a channel mode, return whether it was already set
|
// set a channel mode, return whether it was already set
|
||||||
func (channel *Channel) setMode(mode Mode, enable bool) (already bool) {
|
func (channel *Channel) setMode(mode modes.Mode, enable bool) (already bool) {
|
||||||
channel.stateMutex.Lock()
|
channel.stateMutex.Lock()
|
||||||
already = (channel.flags[mode] == enable)
|
already = (channel.flags[mode] == enable)
|
||||||
if !already {
|
if !already {
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
"github.com/goshuirc/irc-go/ircmsg"
|
"github.com/goshuirc/irc-go/ircmsg"
|
||||||
"github.com/oragono/oragono/irc/caps"
|
"github.com/oragono/oragono/irc/caps"
|
||||||
"github.com/oragono/oragono/irc/custime"
|
"github.com/oragono/oragono/irc/custime"
|
||||||
|
"github.com/oragono/oragono/irc/modes"
|
||||||
"github.com/oragono/oragono/irc/passwd"
|
"github.com/oragono/oragono/irc/passwd"
|
||||||
"github.com/oragono/oragono/irc/sno"
|
"github.com/oragono/oragono/irc/sno"
|
||||||
"github.com/oragono/oragono/irc/utils"
|
"github.com/oragono/oragono/irc/utils"
|
||||||
@ -480,30 +481,30 @@ func awayHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isAway {
|
if isAway {
|
||||||
client.flags[Away] = true
|
client.flags[modes.Away] = true
|
||||||
} else {
|
} else {
|
||||||
delete(client.flags, Away)
|
delete(client.flags, modes.Away)
|
||||||
}
|
}
|
||||||
client.awayMessage = text
|
client.awayMessage = text
|
||||||
|
|
||||||
var op ModeOp
|
var op modes.ModeOp
|
||||||
if client.flags[Away] {
|
if client.flags[modes.Away] {
|
||||||
op = Add
|
op = modes.Add
|
||||||
client.Send(nil, server.name, RPL_NOWAWAY, client.nick, client.t("You have been marked as being away"))
|
client.Send(nil, server.name, RPL_NOWAWAY, client.nick, client.t("You have been marked as being away"))
|
||||||
} else {
|
} else {
|
||||||
op = Remove
|
op = modes.Remove
|
||||||
client.Send(nil, server.name, RPL_UNAWAY, client.nick, client.t("You are no longer marked as being away"))
|
client.Send(nil, server.name, RPL_UNAWAY, client.nick, client.t("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 := modes.ModeChanges{modes.ModeChange{
|
||||||
mode: Away,
|
Mode: modes.Away,
|
||||||
op: op,
|
Op: op,
|
||||||
}}
|
}}
|
||||||
client.Send(nil, server.name, "MODE", client.nick, modech.String())
|
client.Send(nil, server.name, "MODE", client.nick, modech.String())
|
||||||
|
|
||||||
// dispatch away-notify
|
// dispatch away-notify
|
||||||
for friend := range client.Friends(caps.AwayNotify) {
|
for friend := range client.Friends(caps.AwayNotify) {
|
||||||
if client.flags[Away] {
|
if client.flags[modes.Away] {
|
||||||
friend.SendFromClient("", client, nil, "AWAY", client.awayMessage)
|
friend.SendFromClient("", client, nil, "AWAY", client.awayMessage)
|
||||||
} else {
|
} else {
|
||||||
friend.SendFromClient("", client, nil, "AWAY")
|
friend.SendFromClient("", client, nil, "AWAY")
|
||||||
@ -581,7 +582,7 @@ func csHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
|
|
||||||
// DEBUG GCSTATS/NUMGOROUTINE/etc
|
// DEBUG GCSTATS/NUMGOROUTINE/etc
|
||||||
func debugHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
func debugHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
||||||
if !client.flags[Operator] {
|
if !client.flags[modes.Operator] {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -854,7 +855,7 @@ Get an explanation of <argument>, or "index" for a list of help topics.`))
|
|||||||
|
|
||||||
// handle index
|
// handle index
|
||||||
if argument == "index" {
|
if argument == "index" {
|
||||||
if client.flags[Operator] {
|
if client.flags[modes.Operator] {
|
||||||
client.sendHelp("HELP", GetHelpIndex(client.languages, HelpIndexOpers))
|
client.sendHelp("HELP", GetHelpIndex(client.languages, HelpIndexOpers))
|
||||||
} else {
|
} else {
|
||||||
client.sendHelp("HELP", GetHelpIndex(client.languages, HelpIndex))
|
client.sendHelp("HELP", GetHelpIndex(client.languages, HelpIndex))
|
||||||
@ -864,7 +865,7 @@ Get an explanation of <argument>, or "index" for a list of help topics.`))
|
|||||||
|
|
||||||
helpHandler, exists := Help[argument]
|
helpHandler, exists := Help[argument]
|
||||||
|
|
||||||
if exists && (!helpHandler.oper || (helpHandler.oper && client.flags[Operator])) {
|
if exists && (!helpHandler.oper || (helpHandler.oper && client.flags[modes.Operator])) {
|
||||||
if helpHandler.textGenerator != nil {
|
if helpHandler.textGenerator != nil {
|
||||||
client.sendHelp(strings.ToUpper(argument), client.t(helpHandler.textGenerator(client)))
|
client.sendHelp(strings.ToUpper(argument), client.t(helpHandler.textGenerator(client)))
|
||||||
} else {
|
} else {
|
||||||
@ -1336,7 +1337,7 @@ func listHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
|
|
||||||
if len(channels) == 0 {
|
if len(channels) == 0 {
|
||||||
for _, channel := range server.channels.Channels() {
|
for _, channel := range server.channels.Channels() {
|
||||||
if !client.flags[Operator] && channel.flags[Secret] {
|
if !client.flags[modes.Operator] && channel.flags[modes.Secret] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if matcher.Matches(channel) {
|
if matcher.Matches(channel) {
|
||||||
@ -1345,14 +1346,14 @@ func listHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// limit regular users to only listing one channel
|
// limit regular users to only listing one channel
|
||||||
if !client.flags[Operator] {
|
if !client.flags[modes.Operator] {
|
||||||
channels = channels[:1]
|
channels = channels[:1]
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, chname := range channels {
|
for _, chname := range channels {
|
||||||
casefoldedChname, err := CasefoldChannel(chname)
|
casefoldedChname, err := CasefoldChannel(chname)
|
||||||
channel := server.channels.Get(casefoldedChname)
|
channel := server.channels.Get(casefoldedChname)
|
||||||
if err != nil || channel == nil || (!client.flags[Operator] && channel.flags[Secret]) {
|
if err != nil || channel == nil || (!client.flags[modes.Operator] && channel.flags[modes.Secret]) {
|
||||||
if len(chname) > 0 {
|
if len(chname) > 0 {
|
||||||
client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, chname, client.t("No such channel"))
|
client.Send(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, chname, client.t("No such channel"))
|
||||||
}
|
}
|
||||||
@ -1374,10 +1375,10 @@ func lusersHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
|
|
||||||
for _, onlineusers := range server.clients.AllClients() {
|
for _, onlineusers := range server.clients.AllClients() {
|
||||||
totalcount++
|
totalcount++
|
||||||
if onlineusers.flags[Invisible] {
|
if onlineusers.flags[modes.Invisible] {
|
||||||
invisiblecount++
|
invisiblecount++
|
||||||
}
|
}
|
||||||
if onlineusers.flags[Operator] {
|
if onlineusers.flags[modes.Operator] {
|
||||||
opercount++
|
opercount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1409,7 +1410,7 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// applied mode changes
|
// applied mode changes
|
||||||
applied := make(ModeChanges, 0)
|
applied := make(modes.ModeChanges, 0)
|
||||||
|
|
||||||
if 1 < len(msg.Params) {
|
if 1 < len(msg.Params) {
|
||||||
// parse out real mode changes
|
// parse out real mode changes
|
||||||
@ -1431,11 +1432,11 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
// save changes to banlist/exceptlist/invexlist
|
// save changes to banlist/exceptlist/invexlist
|
||||||
var banlistUpdated, exceptlistUpdated, invexlistUpdated bool
|
var banlistUpdated, exceptlistUpdated, invexlistUpdated bool
|
||||||
for _, change := range applied {
|
for _, change := range applied {
|
||||||
if change.mode == BanMask {
|
if change.Mode == modes.BanMask {
|
||||||
banlistUpdated = true
|
banlistUpdated = true
|
||||||
} else if change.mode == ExceptMask {
|
} else if change.Mode == modes.ExceptMask {
|
||||||
exceptlistUpdated = true
|
exceptlistUpdated = true
|
||||||
} else if change.mode == InviteMask {
|
} else if change.Mode == modes.InviteMask {
|
||||||
invexlistUpdated = true
|
invexlistUpdated = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1483,12 +1484,12 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// applied mode changes
|
// applied mode changes
|
||||||
applied := make(ModeChanges, 0)
|
applied := make(modes.ModeChanges, 0)
|
||||||
|
|
||||||
if 1 < len(msg.Params) {
|
if 1 < len(msg.Params) {
|
||||||
// parse out real mode changes
|
// parse out real mode changes
|
||||||
params := msg.Params[1:]
|
params := msg.Params[1:]
|
||||||
changes, unknown := ParseUserModeChanges(params...)
|
changes, unknown := modes.ParseUserModeChanges(params...)
|
||||||
|
|
||||||
// alert for unknown mode changes
|
// alert for unknown mode changes
|
||||||
for char := range unknown {
|
for char := range unknown {
|
||||||
@ -1506,7 +1507,7 @@ func umodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
client.Send(nil, client.nickMaskString, "MODE", targetNick, applied.String())
|
client.Send(nil, client.nickMaskString, "MODE", targetNick, applied.String())
|
||||||
} else if hasPrivs {
|
} else if hasPrivs {
|
||||||
client.Send(nil, target.nickMaskString, RPL_UMODEIS, targetNick, target.ModeString())
|
client.Send(nil, target.nickMaskString, RPL_UMODEIS, targetNick, target.ModeString())
|
||||||
if client.flags[LocalOperator] || client.flags[Operator] {
|
if client.flags[modes.LocalOperator] || client.flags[modes.Operator] {
|
||||||
masks := server.snomasks.String(client)
|
masks := server.snomasks.String(client)
|
||||||
if 0 < len(masks) {
|
if 0 < len(masks) {
|
||||||
client.Send(nil, target.nickMaskString, RPL_SNOMASKIS, targetNick, masks, client.t("Server notice masks"))
|
client.Send(nil, target.nickMaskString, RPL_SNOMASKIS, targetNick, masks, client.t("Server notice masks"))
|
||||||
@ -1687,8 +1688,8 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
if i > maxTargets-1 {
|
if i > maxTargets-1 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
prefixes, targetString := SplitChannelMembershipPrefixes(targetString)
|
prefixes, targetString := modes.SplitChannelMembershipPrefixes(targetString)
|
||||||
lowestPrefix := GetLowestChannelModePrefix(prefixes)
|
lowestPrefix := modes.GetLowestChannelModePrefix(prefixes)
|
||||||
|
|
||||||
target, cerr := CasefoldChannel(targetString)
|
target, cerr := CasefoldChannel(targetString)
|
||||||
if cerr == nil {
|
if cerr == nil {
|
||||||
@ -1727,7 +1728,7 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
msgid := server.generateMessageID()
|
msgid := server.generateMessageID()
|
||||||
// restrict messages appropriately when +R is set
|
// restrict messages appropriately when +R is set
|
||||||
// intentionally make the sending user think the message went through fine
|
// intentionally make the sending user think the message went through fine
|
||||||
if !user.flags[RegisteredOnly] || client.registered {
|
if !user.flags[modes.RegisteredOnly] || client.registered {
|
||||||
user.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "NOTICE", user.nick, splitMsg)
|
user.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "NOTICE", user.nick, splitMsg)
|
||||||
}
|
}
|
||||||
if client.capabilities.Has(caps.EchoMessage) {
|
if client.capabilities.Has(caps.EchoMessage) {
|
||||||
@ -1788,7 +1789,7 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
client.Send(nil, server.name, ERR_PASSWDMISMATCH, client.nick, client.t("Password incorrect"))
|
client.Send(nil, server.name, ERR_PASSWDMISMATCH, client.nick, client.t("Password incorrect"))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if client.flags[Operator] == true {
|
if client.flags[modes.Operator] == true {
|
||||||
client.Send(nil, server.name, ERR_UNKNOWNERROR, "OPER", client.t("You're already opered-up!"))
|
client.Send(nil, server.name, ERR_UNKNOWNERROR, "OPER", client.t("You're already opered-up!"))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -1803,7 +1804,7 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
client.flags[Operator] = true
|
client.flags[modes.Operator] = true
|
||||||
client.operName = name
|
client.operName = name
|
||||||
client.class = oper.Class
|
client.class = oper.Class
|
||||||
client.whoisLine = oper.WhoisLine
|
client.whoisLine = oper.WhoisLine
|
||||||
@ -1819,9 +1820,9 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set new modes
|
// set new modes
|
||||||
var applied ModeChanges
|
var applied modes.ModeChanges
|
||||||
if 0 < len(oper.Modes) {
|
if 0 < len(oper.Modes) {
|
||||||
modeChanges, unknownChanges := ParseUserModeChanges(strings.Split(oper.Modes, " ")...)
|
modeChanges, unknownChanges := modes.ParseUserModeChanges(strings.Split(oper.Modes, " ")...)
|
||||||
applied = client.applyUserModeChanges(true, modeChanges)
|
applied = client.applyUserModeChanges(true, modeChanges)
|
||||||
if 0 < len(unknownChanges) {
|
if 0 < len(unknownChanges) {
|
||||||
var runes string
|
var runes string
|
||||||
@ -1834,9 +1835,9 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
|
|
||||||
client.Send(nil, server.name, RPL_YOUREOPER, client.nick, client.t("You are now an IRC operator"))
|
client.Send(nil, server.name, RPL_YOUREOPER, client.nick, client.t("You are now an IRC operator"))
|
||||||
|
|
||||||
applied = append(applied, ModeChange{
|
applied = append(applied, modes.ModeChange{
|
||||||
mode: Operator,
|
Mode: modes.Operator,
|
||||||
op: Add,
|
Op: modes.Add,
|
||||||
})
|
})
|
||||||
client.Send(nil, server.name, "MODE", client.nick, applied.String())
|
client.Send(nil, server.name, "MODE", client.nick, applied.String())
|
||||||
|
|
||||||
@ -1912,8 +1913,8 @@ func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool
|
|||||||
if i > maxTargets-1 {
|
if i > maxTargets-1 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
prefixes, targetString := SplitChannelMembershipPrefixes(targetString)
|
prefixes, targetString := modes.SplitChannelMembershipPrefixes(targetString)
|
||||||
lowestPrefix := GetLowestChannelModePrefix(prefixes)
|
lowestPrefix := modes.GetLowestChannelModePrefix(prefixes)
|
||||||
|
|
||||||
// eh, no need to notify them
|
// eh, no need to notify them
|
||||||
if len(targetString) < 1 {
|
if len(targetString) < 1 {
|
||||||
@ -1955,13 +1956,13 @@ func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool
|
|||||||
msgid := server.generateMessageID()
|
msgid := server.generateMessageID()
|
||||||
// restrict messages appropriately when +R is set
|
// restrict messages appropriately when +R is set
|
||||||
// intentionally make the sending user think the message went through fine
|
// intentionally make the sending user think the message went through fine
|
||||||
if !user.flags[RegisteredOnly] || client.registered {
|
if !user.flags[modes.RegisteredOnly] || client.registered {
|
||||||
user.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "PRIVMSG", user.nick, splitMsg)
|
user.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "PRIVMSG", user.nick, splitMsg)
|
||||||
}
|
}
|
||||||
if client.capabilities.Has(caps.EchoMessage) {
|
if client.capabilities.Has(caps.EchoMessage) {
|
||||||
client.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "PRIVMSG", user.nick, splitMsg)
|
client.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "PRIVMSG", user.nick, splitMsg)
|
||||||
}
|
}
|
||||||
if user.flags[Away] {
|
if user.flags[modes.Away] {
|
||||||
//TODO(dan): possibly implement cooldown of away notifications to users
|
//TODO(dan): possibly implement cooldown of away notifications to users
|
||||||
client.Send(nil, server.name, RPL_AWAY, user.nick, user.awayMessage)
|
client.Send(nil, server.name, RPL_AWAY, user.nick, user.awayMessage)
|
||||||
}
|
}
|
||||||
@ -2059,7 +2060,7 @@ func renameHandler(server *Server, client *Client, msg ircmsg.IrcMessage) (resul
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
//TODO(dan): allow IRCops to do this?
|
//TODO(dan): allow IRCops to do this?
|
||||||
if !channel.ClientIsAtLeast(client, Operator) {
|
if !channel.ClientIsAtLeast(client, modes.Operator) {
|
||||||
errorResponse(RenamePrivsNeeded, oldName)
|
errorResponse(RenamePrivsNeeded, oldName)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -2171,8 +2172,8 @@ func tagmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
if i > maxTargets-1 {
|
if i > maxTargets-1 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
prefixes, targetString := SplitChannelMembershipPrefixes(targetString)
|
prefixes, targetString := modes.SplitChannelMembershipPrefixes(targetString)
|
||||||
lowestPrefix := GetLowestChannelModePrefix(prefixes)
|
lowestPrefix := modes.GetLowestChannelModePrefix(prefixes)
|
||||||
|
|
||||||
// eh, no need to notify them
|
// eh, no need to notify them
|
||||||
if len(targetString) < 1 {
|
if len(targetString) < 1 {
|
||||||
@ -2212,7 +2213,7 @@ func tagmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
if client.capabilities.Has(caps.EchoMessage) {
|
if client.capabilities.Has(caps.EchoMessage) {
|
||||||
client.SendFromClient(msgid, client, clientOnlyTags, "TAGMSG", user.nick)
|
client.SendFromClient(msgid, client, clientOnlyTags, "TAGMSG", user.nick)
|
||||||
}
|
}
|
||||||
if user.flags[Away] {
|
if user.flags[modes.Away] {
|
||||||
//TODO(dan): possibly implement cooldown of away notifications to users
|
//TODO(dan): possibly implement cooldown of away notifications to users
|
||||||
client.Send(nil, server.name, RPL_AWAY, user.nick, user.awayMessage)
|
client.Send(nil, server.name, RPL_AWAY, user.nick, user.awayMessage)
|
||||||
}
|
}
|
||||||
@ -2424,10 +2425,10 @@ func userhostHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool
|
|||||||
|
|
||||||
var isOper, isAway string
|
var isOper, isAway string
|
||||||
|
|
||||||
if target.flags[Operator] {
|
if target.flags[modes.Operator] {
|
||||||
isOper = "*"
|
isOper = "*"
|
||||||
}
|
}
|
||||||
if target.flags[Away] {
|
if target.flags[modes.Away] {
|
||||||
isAway = "-"
|
isAway = "-"
|
||||||
} else {
|
} else {
|
||||||
isAway = "+"
|
isAway = "+"
|
||||||
@ -2478,7 +2479,7 @@ func webircHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
lkey := strings.ToLower(key)
|
lkey := strings.ToLower(key)
|
||||||
if lkey == "tls" || lkey == "secure" {
|
if lkey == "tls" || lkey == "secure" {
|
||||||
// only accept "tls" flag if the gateway's connection to us is secure as well
|
// only accept "tls" flag if the gateway's connection to us is secure as well
|
||||||
if client.flags[TLS] || utils.AddrIsLocal(client.socket.conn.RemoteAddr()) {
|
if client.flags[modes.TLS] || utils.AddrIsLocal(client.socket.conn.RemoteAddr()) {
|
||||||
secure = true
|
secure = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2567,7 +2568,7 @@ func whoisHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if client.flags[Operator] {
|
if client.flags[modes.Operator] {
|
||||||
masks := strings.Split(masksString, ",")
|
masks := strings.Split(masksString, ",")
|
||||||
for _, mask := range masks {
|
for _, mask := range masks {
|
||||||
casefoldedMask, err := Casefold(mask)
|
casefoldedMask, err := Casefold(mask)
|
||||||
|
415
irc/modes.go
415
irc/modes.go
@ -9,298 +9,59 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/oragono/oragono/irc/modes"
|
||||||
"github.com/oragono/oragono/irc/sno"
|
"github.com/oragono/oragono/irc/sno"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ModeOp is an operation performed with modes
|
|
||||||
type ModeOp rune
|
|
||||||
|
|
||||||
func (op ModeOp) String() string {
|
|
||||||
return string(op)
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Add is used when adding the given key.
|
|
||||||
Add ModeOp = '+'
|
|
||||||
// List is used when listing modes (for instance, listing the current bans on a channel).
|
|
||||||
List ModeOp = '='
|
|
||||||
// Remove is used when taking away the given key.
|
|
||||||
Remove ModeOp = '-'
|
|
||||||
)
|
|
||||||
|
|
||||||
// Mode represents a user/channel/server mode
|
|
||||||
type Mode rune
|
|
||||||
|
|
||||||
func (mode Mode) String() string {
|
|
||||||
return string(mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModeChange is a single mode changing
|
|
||||||
type ModeChange struct {
|
|
||||||
mode Mode
|
|
||||||
op ModeOp
|
|
||||||
arg string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (change *ModeChange) String() (str string) {
|
|
||||||
if (change.op == Add) || (change.op == Remove) {
|
|
||||||
str = change.op.String()
|
|
||||||
}
|
|
||||||
str += change.mode.String()
|
|
||||||
if change.arg != "" {
|
|
||||||
str += " " + change.arg
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModeChanges are a collection of 'ModeChange's
|
|
||||||
type ModeChanges []ModeChange
|
|
||||||
|
|
||||||
func (changes ModeChanges) String() string {
|
|
||||||
if len(changes) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
op := changes[0].op
|
|
||||||
str := changes[0].op.String()
|
|
||||||
|
|
||||||
for _, change := range changes {
|
|
||||||
if change.op != op {
|
|
||||||
op = change.op
|
|
||||||
str += change.op.String()
|
|
||||||
}
|
|
||||||
str += change.mode.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, change := range changes {
|
|
||||||
if change.arg == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
str += " " + change.arg
|
|
||||||
}
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modes is just a raw list of modes
|
|
||||||
type Modes []Mode
|
|
||||||
|
|
||||||
func (modes Modes) String() string {
|
|
||||||
strs := make([]string, len(modes))
|
|
||||||
for index, mode := range modes {
|
|
||||||
strs[index] = mode.String()
|
|
||||||
}
|
|
||||||
return strings.Join(strs, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// User Modes
|
|
||||||
const (
|
|
||||||
Away Mode = 'a'
|
|
||||||
Bot Mode = 'B'
|
|
||||||
Invisible Mode = 'i'
|
|
||||||
LocalOperator Mode = 'O'
|
|
||||||
Operator Mode = 'o'
|
|
||||||
Restricted Mode = 'r'
|
|
||||||
RegisteredOnly Mode = 'R'
|
|
||||||
ServerNotice Mode = 's'
|
|
||||||
TLS Mode = 'Z'
|
|
||||||
UserRoleplaying Mode = 'E'
|
|
||||||
WallOps Mode = 'w'
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// SupportedUserModes are the user modes that we actually support (modifying).
|
|
||||||
SupportedUserModes = Modes{
|
|
||||||
Away, Bot, Invisible, Operator, RegisteredOnly, ServerNotice, UserRoleplaying,
|
|
||||||
}
|
|
||||||
// supportedUserModesString acts as a cache for when we introduce users
|
|
||||||
supportedUserModesString = SupportedUserModes.String()
|
|
||||||
)
|
|
||||||
|
|
||||||
// Channel Modes
|
|
||||||
const (
|
|
||||||
BanMask Mode = 'b' // arg
|
|
||||||
ChanRoleplaying Mode = 'E' // flag
|
|
||||||
ExceptMask Mode = 'e' // arg
|
|
||||||
InviteMask Mode = 'I' // arg
|
|
||||||
InviteOnly Mode = 'i' // flag
|
|
||||||
Key Mode = 'k' // flag arg
|
|
||||||
Moderated Mode = 'm' // flag
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ChannelFounder Mode = 'q' // arg
|
|
||||||
ChannelAdmin Mode = 'a' // arg
|
|
||||||
ChannelOperator Mode = 'o' // arg
|
|
||||||
Halfop Mode = 'h' // arg
|
|
||||||
Voice Mode = 'v' // arg
|
|
||||||
|
|
||||||
// SupportedChannelModes are the channel modes that we support.
|
|
||||||
SupportedChannelModes = Modes{
|
|
||||||
BanMask, ChanRoleplaying, ExceptMask, InviteMask, InviteOnly, Key,
|
|
||||||
Moderated, NoOutside, OpOnlyTopic, RegisteredOnly, Secret, UserLimit,
|
|
||||||
}
|
|
||||||
// supportedChannelModesString acts as a cache for when we introduce users
|
|
||||||
supportedChannelModesString = SupportedChannelModes.String()
|
|
||||||
|
|
||||||
// DefaultChannelModes are enabled on brand new channels when they're created.
|
// DefaultChannelModes are enabled on brand new channels when they're created.
|
||||||
// this can be overridden in the `channels` config, with the `default-modes` key
|
// this can be overridden in the `channels` config, with the `default-modes` key
|
||||||
DefaultChannelModes = Modes{
|
DefaultChannelModes = modes.Modes{
|
||||||
NoOutside, OpOnlyTopic,
|
modes.NoOutside, modes.OpOnlyTopic,
|
||||||
}
|
|
||||||
|
|
||||||
// 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 = Modes{
|
|
||||||
ChannelFounder, ChannelAdmin, ChannelOperator, Halfop,
|
|
||||||
}
|
|
||||||
|
|
||||||
ChannelModePrefixes = map[Mode]string{
|
|
||||||
ChannelFounder: "~",
|
|
||||||
ChannelAdmin: "&",
|
|
||||||
ChannelOperator: "@",
|
|
||||||
Halfop: "%",
|
|
||||||
Voice: "+",
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
//
|
|
||||||
// channel membership prefixes
|
|
||||||
//
|
|
||||||
|
|
||||||
// SplitChannelMembershipPrefixes takes a target and returns the prefixes on it, then the name.
|
|
||||||
func SplitChannelMembershipPrefixes(target string) (prefixes string, name string) {
|
|
||||||
name = target
|
|
||||||
for {
|
|
||||||
if len(name) > 0 && strings.Contains("~&@%+", string(name[0])) {
|
|
||||||
prefixes += string(name[0])
|
|
||||||
name = name[1:]
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return prefixes, name
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLowestChannelModePrefix returns the lowest channel prefix mode out of the given prefixes.
|
|
||||||
func GetLowestChannelModePrefix(prefixes string) *Mode {
|
|
||||||
var lowest *Mode
|
|
||||||
|
|
||||||
if strings.Contains(prefixes, "+") {
|
|
||||||
lowest = &Voice
|
|
||||||
} else {
|
|
||||||
for i, mode := range ChannelPrivModes {
|
|
||||||
if strings.Contains(prefixes, ChannelModePrefixes[mode]) {
|
|
||||||
lowest = &ChannelPrivModes[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return lowest
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// commands
|
|
||||||
//
|
|
||||||
|
|
||||||
// ParseUserModeChanges returns the valid changes, and the list of unknown chars.
|
|
||||||
func ParseUserModeChanges(params ...string) (ModeChanges, map[rune]bool) {
|
|
||||||
changes := make(ModeChanges, 0)
|
|
||||||
unknown := make(map[rune]bool)
|
|
||||||
|
|
||||||
op := List
|
|
||||||
|
|
||||||
if 0 < len(params) {
|
|
||||||
modeArg := params[0]
|
|
||||||
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 ServerNotice:
|
|
||||||
// always require arg
|
|
||||||
if len(params) > skipArgs {
|
|
||||||
change.arg = params[skipArgs]
|
|
||||||
skipArgs++
|
|
||||||
} else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var isKnown bool
|
|
||||||
for _, supportedMode := range SupportedUserModes {
|
|
||||||
if rune(supportedMode) == mode {
|
|
||||||
isKnown = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !isKnown {
|
|
||||||
unknown[mode] = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
changes = append(changes, change)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return changes, unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
// applyUserModeChanges applies the given changes, and returns the applied changes.
|
// applyUserModeChanges applies the given changes, and returns the applied changes.
|
||||||
func (client *Client) applyUserModeChanges(force bool, changes ModeChanges) ModeChanges {
|
func (client *Client) applyUserModeChanges(force bool, changes modes.ModeChanges) modes.ModeChanges {
|
||||||
applied := make(ModeChanges, 0)
|
applied := make(modes.ModeChanges, 0)
|
||||||
|
|
||||||
for _, change := range changes {
|
for _, change := range changes {
|
||||||
switch change.mode {
|
switch change.Mode {
|
||||||
case Bot, Invisible, WallOps, UserRoleplaying, Operator, LocalOperator, RegisteredOnly:
|
case modes.Bot, modes.Invisible, modes.WallOps, modes.UserRoleplaying, modes.Operator, modes.LocalOperator, modes.RegisteredOnly:
|
||||||
switch change.op {
|
switch change.Op {
|
||||||
case Add:
|
case modes.Add:
|
||||||
if !force && (change.mode == Operator || change.mode == LocalOperator) {
|
if !force && (change.Mode == modes.Operator || change.Mode == modes.LocalOperator) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if client.flags[change.mode] {
|
if client.flags[change.Mode] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
client.flags[change.mode] = true
|
client.flags[change.Mode] = true
|
||||||
applied = append(applied, change)
|
applied = append(applied, change)
|
||||||
|
|
||||||
case Remove:
|
case modes.Remove:
|
||||||
if !client.flags[change.mode] {
|
if !client.flags[change.Mode] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
delete(client.flags, change.mode)
|
delete(client.flags, change.Mode)
|
||||||
applied = append(applied, change)
|
applied = append(applied, change)
|
||||||
}
|
}
|
||||||
|
|
||||||
case ServerNotice:
|
case modes.ServerNotice:
|
||||||
if !client.flags[Operator] {
|
if !client.flags[modes.Operator] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var masks []sno.Mask
|
var masks []sno.Mask
|
||||||
if change.op == Add || change.op == Remove {
|
if change.Op == modes.Add || change.Op == modes.Remove {
|
||||||
for _, char := range change.arg {
|
for _, char := range change.Arg {
|
||||||
masks = append(masks, sno.Mask(char))
|
masks = append(masks, sno.Mask(char))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if change.op == Add {
|
if change.Op == modes.Add {
|
||||||
client.server.snomasks.AddMasks(client, masks...)
|
client.server.snomasks.AddMasks(client, masks...)
|
||||||
applied = append(applied, change)
|
applied = append(applied, change)
|
||||||
} else if change.op == Remove {
|
} else if change.Op == modes.Remove {
|
||||||
client.server.snomasks.RemoveMasks(client, masks...)
|
client.server.snomasks.RemoveMasks(client, masks...)
|
||||||
applied = append(applied, change)
|
applied = append(applied, change)
|
||||||
}
|
}
|
||||||
@ -314,28 +75,28 @@ func (client *Client) applyUserModeChanges(force bool, changes ModeChanges) Mode
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ParseDefaultChannelModes parses the `default-modes` line of the config
|
// ParseDefaultChannelModes parses the `default-modes` line of the config
|
||||||
func ParseDefaultChannelModes(config *Config) Modes {
|
func ParseDefaultChannelModes(config *Config) modes.Modes {
|
||||||
if config.Channels.DefaultModes == nil {
|
if config.Channels.DefaultModes == nil {
|
||||||
// not present in config, fall back to compile-time default
|
// not present in config, fall back to compile-time default
|
||||||
return DefaultChannelModes
|
return DefaultChannelModes
|
||||||
}
|
}
|
||||||
modeChangeStrings := strings.Split(strings.TrimSpace(*config.Channels.DefaultModes), " ")
|
modeChangeStrings := strings.Split(strings.TrimSpace(*config.Channels.DefaultModes), " ")
|
||||||
modeChanges, _ := ParseChannelModeChanges(modeChangeStrings...)
|
modeChanges, _ := ParseChannelModeChanges(modeChangeStrings...)
|
||||||
defaultChannelModes := make(Modes, 0)
|
defaultChannelModes := make(modes.Modes, 0)
|
||||||
for _, modeChange := range modeChanges {
|
for _, modeChange := range modeChanges {
|
||||||
if modeChange.op == Add {
|
if modeChange.Op == modes.Add {
|
||||||
defaultChannelModes = append(defaultChannelModes, modeChange.mode)
|
defaultChannelModes = append(defaultChannelModes, modeChange.Mode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return defaultChannelModes
|
return defaultChannelModes
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseChannelModeChanges returns the valid changes, and the list of unknown chars.
|
// ParseChannelModeChanges returns the valid changes, and the list of unknown chars.
|
||||||
func ParseChannelModeChanges(params ...string) (ModeChanges, map[rune]bool) {
|
func ParseChannelModeChanges(params ...string) (modes.ModeChanges, map[rune]bool) {
|
||||||
changes := make(ModeChanges, 0)
|
changes := make(modes.ModeChanges, 0)
|
||||||
unknown := make(map[rune]bool)
|
unknown := make(map[rune]bool)
|
||||||
|
|
||||||
op := List
|
op := modes.List
|
||||||
|
|
||||||
if 0 < len(params) {
|
if 0 < len(params) {
|
||||||
modeArg := params[0]
|
modeArg := params[0]
|
||||||
@ -343,35 +104,35 @@ func ParseChannelModeChanges(params ...string) (ModeChanges, map[rune]bool) {
|
|||||||
|
|
||||||
for _, mode := range modeArg {
|
for _, mode := range modeArg {
|
||||||
if mode == '-' || mode == '+' {
|
if mode == '-' || mode == '+' {
|
||||||
op = ModeOp(mode)
|
op = modes.ModeOp(mode)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
change := ModeChange{
|
change := modes.ModeChange{
|
||||||
mode: Mode(mode),
|
Mode: modes.Mode(mode),
|
||||||
op: op,
|
Op: op,
|
||||||
}
|
}
|
||||||
|
|
||||||
// put arg into modechange if needed
|
// put arg into modechange if needed
|
||||||
switch Mode(mode) {
|
switch modes.Mode(mode) {
|
||||||
case BanMask, ExceptMask, InviteMask:
|
case modes.BanMask, modes.ExceptMask, modes.InviteMask:
|
||||||
if len(params) > skipArgs {
|
if len(params) > skipArgs {
|
||||||
change.arg = params[skipArgs]
|
change.Arg = params[skipArgs]
|
||||||
skipArgs++
|
skipArgs++
|
||||||
} else {
|
} else {
|
||||||
change.op = List
|
change.Op = modes.List
|
||||||
}
|
}
|
||||||
case ChannelFounder, ChannelAdmin, ChannelOperator, Halfop, Voice:
|
case modes.ChannelFounder, modes.ChannelAdmin, modes.ChannelOperator, modes.Halfop, modes.Voice:
|
||||||
if len(params) > skipArgs {
|
if len(params) > skipArgs {
|
||||||
change.arg = params[skipArgs]
|
change.Arg = params[skipArgs]
|
||||||
skipArgs++
|
skipArgs++
|
||||||
} else {
|
} else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
case Key, UserLimit:
|
case modes.Key, modes.UserLimit:
|
||||||
// don't require value when removing
|
// don't require value when removing
|
||||||
if change.op == Add {
|
if change.Op == modes.Add {
|
||||||
if len(params) > skipArgs {
|
if len(params) > skipArgs {
|
||||||
change.arg = params[skipArgs]
|
change.Arg = params[skipArgs]
|
||||||
skipArgs++
|
skipArgs++
|
||||||
} else {
|
} else {
|
||||||
continue
|
continue
|
||||||
@ -380,19 +141,19 @@ func ParseChannelModeChanges(params ...string) (ModeChanges, map[rune]bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var isKnown bool
|
var isKnown bool
|
||||||
for _, supportedMode := range SupportedChannelModes {
|
for _, supportedMode := range modes.SupportedChannelModes {
|
||||||
if rune(supportedMode) == mode {
|
if rune(supportedMode) == mode {
|
||||||
isKnown = true
|
isKnown = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, supportedMode := range ChannelPrivModes {
|
for _, supportedMode := range modes.ChannelPrivModes {
|
||||||
if rune(supportedMode) == mode {
|
if rune(supportedMode) == mode {
|
||||||
isKnown = true
|
isKnown = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if mode == rune(Voice) {
|
if mode == rune(modes.Voice) {
|
||||||
isKnown = true
|
isKnown = true
|
||||||
}
|
}
|
||||||
if !isKnown {
|
if !isKnown {
|
||||||
@ -408,40 +169,40 @@ func ParseChannelModeChanges(params ...string) (ModeChanges, map[rune]bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ApplyChannelModeChanges applies a given set of mode changes.
|
// ApplyChannelModeChanges applies a given set of mode changes.
|
||||||
func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, changes ModeChanges) ModeChanges {
|
func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, changes modes.ModeChanges) modes.ModeChanges {
|
||||||
// so we only output one warning for each list type when full
|
// so we only output one warning for each list type when full
|
||||||
listFullWarned := make(map[Mode]bool)
|
listFullWarned := make(map[modes.Mode]bool)
|
||||||
|
|
||||||
clientIsOp := channel.ClientIsAtLeast(client, ChannelOperator)
|
clientIsOp := channel.ClientIsAtLeast(client, modes.ChannelOperator)
|
||||||
var alreadySentPrivError bool
|
var alreadySentPrivError bool
|
||||||
|
|
||||||
applied := make(ModeChanges, 0)
|
applied := make(modes.ModeChanges, 0)
|
||||||
|
|
||||||
isListOp := func(change ModeChange) bool {
|
isListOp := func(change modes.ModeChange) bool {
|
||||||
return (change.op == List) || (change.arg == "")
|
return (change.Op == modes.List) || (change.Arg == "")
|
||||||
}
|
}
|
||||||
|
|
||||||
hasPrivs := func(change ModeChange) bool {
|
hasPrivs := func(change modes.ModeChange) bool {
|
||||||
if isSamode {
|
if isSamode {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
switch change.mode {
|
switch change.Mode {
|
||||||
case ChannelFounder, ChannelAdmin, ChannelOperator, Halfop, Voice:
|
case modes.ChannelFounder, modes.ChannelAdmin, modes.ChannelOperator, modes.Halfop, modes.Voice:
|
||||||
// Admins can't give other people Admin or remove it from others
|
// Admins can't give other people Admin or remove it from others
|
||||||
if change.mode == ChannelAdmin {
|
if change.Mode == modes.ChannelAdmin {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if change.op == List {
|
if change.Op == modes.List {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
cfarg, _ := CasefoldName(change.arg)
|
cfarg, _ := CasefoldName(change.Arg)
|
||||||
if change.op == Remove && cfarg == client.nickCasefolded {
|
if change.Op == modes.Remove && cfarg == client.nickCasefolded {
|
||||||
// "There is no restriction, however, on anyone `deopping' themselves"
|
// "There is no restriction, however, on anyone `deopping' themselves"
|
||||||
// <https://tools.ietf.org/html/rfc2812#section-3.1.5>
|
// <https://tools.ietf.org/html/rfc2812#section-3.1.5>
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return channel.ClientIsAtLeast(client, change.mode)
|
return channel.ClientIsAtLeast(client, change.Mode)
|
||||||
case BanMask:
|
case modes.BanMask:
|
||||||
// #163: allow unprivileged users to list ban masks
|
// #163: allow unprivileged users to list ban masks
|
||||||
return clientIsOp || isListOp(change)
|
return clientIsOp || isListOp(change)
|
||||||
default:
|
default:
|
||||||
@ -458,77 +219,77 @@ func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, c
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
switch change.mode {
|
switch change.Mode {
|
||||||
case BanMask, ExceptMask, InviteMask:
|
case modes.BanMask, modes.ExceptMask, modes.InviteMask:
|
||||||
if isListOp(change) {
|
if isListOp(change) {
|
||||||
channel.ShowMaskList(client, change.mode)
|
channel.ShowMaskList(client, change.Mode)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// confirm mask looks valid
|
// confirm mask looks valid
|
||||||
mask, err := Casefold(change.arg)
|
mask, err := Casefold(change.Arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
switch change.op {
|
switch change.Op {
|
||||||
case Add:
|
case modes.Add:
|
||||||
if channel.lists[change.mode].Length() >= client.server.Limits().ChanListModes {
|
if channel.lists[change.Mode].Length() >= client.server.Limits().ChanListModes {
|
||||||
if !listFullWarned[change.mode] {
|
if !listFullWarned[change.Mode] {
|
||||||
client.Send(nil, client.server.name, ERR_BANLISTFULL, client.Nick(), channel.Name(), change.mode.String(), client.t("Channel list is full"))
|
client.Send(nil, client.server.name, ERR_BANLISTFULL, client.Nick(), channel.Name(), change.Mode.String(), client.t("Channel list is full"))
|
||||||
listFullWarned[change.mode] = true
|
listFullWarned[change.Mode] = true
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.lists[change.mode].Add(mask)
|
channel.lists[change.Mode].Add(mask)
|
||||||
applied = append(applied, change)
|
applied = append(applied, change)
|
||||||
|
|
||||||
case Remove:
|
case modes.Remove:
|
||||||
channel.lists[change.mode].Remove(mask)
|
channel.lists[change.Mode].Remove(mask)
|
||||||
applied = append(applied, change)
|
applied = append(applied, change)
|
||||||
}
|
}
|
||||||
|
|
||||||
case UserLimit:
|
case modes.UserLimit:
|
||||||
switch change.op {
|
switch change.Op {
|
||||||
case Add:
|
case modes.Add:
|
||||||
val, err := strconv.ParseUint(change.arg, 10, 64)
|
val, err := strconv.ParseUint(change.Arg, 10, 64)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
channel.setUserLimit(val)
|
channel.setUserLimit(val)
|
||||||
applied = append(applied, change)
|
applied = append(applied, change)
|
||||||
}
|
}
|
||||||
|
|
||||||
case Remove:
|
case modes.Remove:
|
||||||
channel.setUserLimit(0)
|
channel.setUserLimit(0)
|
||||||
applied = append(applied, change)
|
applied = append(applied, change)
|
||||||
}
|
}
|
||||||
|
|
||||||
case Key:
|
case modes.Key:
|
||||||
switch change.op {
|
switch change.Op {
|
||||||
case Add:
|
case modes.Add:
|
||||||
channel.setKey(change.arg)
|
channel.setKey(change.Arg)
|
||||||
|
|
||||||
case Remove:
|
case modes.Remove:
|
||||||
channel.setKey("")
|
channel.setKey("")
|
||||||
}
|
}
|
||||||
applied = append(applied, change)
|
applied = append(applied, change)
|
||||||
|
|
||||||
case InviteOnly, Moderated, NoOutside, OpOnlyTopic, RegisteredOnly, Secret, ChanRoleplaying:
|
case modes.InviteOnly, modes.Moderated, modes.NoOutside, modes.OpOnlyTopic, modes.RegisteredOnly, modes.Secret, modes.ChanRoleplaying:
|
||||||
if change.op == List {
|
if change.Op == modes.List {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
already := channel.setMode(change.mode, change.op == Add)
|
already := channel.setMode(change.Mode, change.Op == modes.Add)
|
||||||
if !already {
|
if !already {
|
||||||
applied = append(applied, change)
|
applied = append(applied, change)
|
||||||
}
|
}
|
||||||
|
|
||||||
case ChannelFounder, ChannelAdmin, ChannelOperator, Halfop, Voice:
|
case modes.ChannelFounder, modes.ChannelAdmin, modes.ChannelOperator, modes.Halfop, modes.Voice:
|
||||||
if change.op == List {
|
if change.Op == modes.List {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
change := channel.applyModeMemberNoMutex(client, change.mode, change.op, change.arg)
|
change := channel.applyModeMemberNoMutex(client, change.Mode, change.Op, change.Arg)
|
||||||
if change != nil {
|
if change != nil {
|
||||||
applied = append(applied, *change)
|
applied = append(applied, *change)
|
||||||
}
|
}
|
||||||
|
286
irc/modes/modes.go
Normal file
286
irc/modes/modes.go
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
// Copyright (c) 2012-2014 Jeremy Latt
|
||||||
|
// Copyright (c) 2014-2015 Edmund Huber
|
||||||
|
// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
|
||||||
|
// released under the MIT license
|
||||||
|
|
||||||
|
package modes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// SupportedUserModes are the user modes that we actually support (modifying).
|
||||||
|
SupportedUserModes = Modes{
|
||||||
|
Away, Bot, Invisible, Operator, RegisteredOnly, ServerNotice, UserRoleplaying,
|
||||||
|
}
|
||||||
|
|
||||||
|
// SupportedChannelModes are the channel modes that we support.
|
||||||
|
SupportedChannelModes = Modes{
|
||||||
|
BanMask, ChanRoleplaying, ExceptMask, InviteMask, InviteOnly, Key,
|
||||||
|
Moderated, NoOutside, OpOnlyTopic, RegisteredOnly, Secret, UserLimit,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ModeOp is an operation performed with modes
|
||||||
|
type ModeOp rune
|
||||||
|
|
||||||
|
func (op ModeOp) String() string {
|
||||||
|
return string(op)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Add is used when adding the given key.
|
||||||
|
Add ModeOp = '+'
|
||||||
|
// List is used when listing modes (for instance, listing the current bans on a channel).
|
||||||
|
List ModeOp = '='
|
||||||
|
// Remove is used when taking away the given key.
|
||||||
|
Remove ModeOp = '-'
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mode represents a user/channel/server mode
|
||||||
|
type Mode rune
|
||||||
|
|
||||||
|
func (mode Mode) String() string {
|
||||||
|
return string(mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModeChange is a single mode changing
|
||||||
|
type ModeChange struct {
|
||||||
|
Mode Mode
|
||||||
|
Op ModeOp
|
||||||
|
Arg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (change *ModeChange) String() (str string) {
|
||||||
|
if (change.Op == Add) || (change.Op == Remove) {
|
||||||
|
str = change.Op.String()
|
||||||
|
}
|
||||||
|
str += change.Mode.String()
|
||||||
|
if change.Arg != "" {
|
||||||
|
str += " " + change.Arg
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModeChanges are a collection of 'ModeChange's
|
||||||
|
type ModeChanges []ModeChange
|
||||||
|
|
||||||
|
func (changes ModeChanges) String() string {
|
||||||
|
if len(changes) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
op := changes[0].Op
|
||||||
|
str := changes[0].Op.String()
|
||||||
|
|
||||||
|
for _, change := range changes {
|
||||||
|
if change.Op != op {
|
||||||
|
op = change.Op
|
||||||
|
str += change.Op.String()
|
||||||
|
}
|
||||||
|
str += change.Mode.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, change := range changes {
|
||||||
|
if change.Arg == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
str += " " + change.Arg
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modes is just a raw list of modes
|
||||||
|
type Modes []Mode
|
||||||
|
|
||||||
|
func (modes Modes) String() string {
|
||||||
|
strs := make([]string, len(modes))
|
||||||
|
for index, mode := range modes {
|
||||||
|
strs[index] = mode.String()
|
||||||
|
}
|
||||||
|
return strings.Join(strs, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// User Modes
|
||||||
|
const (
|
||||||
|
Away Mode = 'a'
|
||||||
|
Bot Mode = 'B'
|
||||||
|
Invisible Mode = 'i'
|
||||||
|
LocalOperator Mode = 'O'
|
||||||
|
Operator Mode = 'o'
|
||||||
|
Restricted Mode = 'r'
|
||||||
|
RegisteredOnly Mode = 'R'
|
||||||
|
ServerNotice Mode = 's'
|
||||||
|
TLS Mode = 'Z'
|
||||||
|
UserRoleplaying Mode = 'E'
|
||||||
|
WallOps Mode = 'w'
|
||||||
|
)
|
||||||
|
|
||||||
|
// Channel Modes
|
||||||
|
const (
|
||||||
|
BanMask Mode = 'b' // arg
|
||||||
|
ChanRoleplaying Mode = 'E' // flag
|
||||||
|
ExceptMask Mode = 'e' // arg
|
||||||
|
InviteMask Mode = 'I' // arg
|
||||||
|
InviteOnly Mode = 'i' // flag
|
||||||
|
Key Mode = 'k' // flag arg
|
||||||
|
Moderated Mode = 'm' // flag
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ChannelFounder Mode = 'q' // arg
|
||||||
|
ChannelAdmin Mode = 'a' // arg
|
||||||
|
ChannelOperator Mode = 'o' // arg
|
||||||
|
Halfop Mode = 'h' // arg
|
||||||
|
Voice Mode = 'v' // arg
|
||||||
|
|
||||||
|
// 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 = Modes{
|
||||||
|
ChannelFounder, ChannelAdmin, ChannelOperator, Halfop,
|
||||||
|
}
|
||||||
|
|
||||||
|
ChannelModePrefixes = map[Mode]string{
|
||||||
|
ChannelFounder: "~",
|
||||||
|
ChannelAdmin: "&",
|
||||||
|
ChannelOperator: "@",
|
||||||
|
Halfop: "%",
|
||||||
|
Voice: "+",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
//
|
||||||
|
// channel membership prefixes
|
||||||
|
//
|
||||||
|
|
||||||
|
// SplitChannelMembershipPrefixes takes a target and returns the prefixes on it, then the name.
|
||||||
|
func SplitChannelMembershipPrefixes(target string) (prefixes string, name string) {
|
||||||
|
name = target
|
||||||
|
for {
|
||||||
|
if len(name) > 0 && strings.Contains("~&@%+", string(name[0])) {
|
||||||
|
prefixes += string(name[0])
|
||||||
|
name = name[1:]
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return prefixes, name
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLowestChannelModePrefix returns the lowest channel prefix mode out of the given prefixes.
|
||||||
|
func GetLowestChannelModePrefix(prefixes string) *Mode {
|
||||||
|
var lowest *Mode
|
||||||
|
|
||||||
|
if strings.Contains(prefixes, "+") {
|
||||||
|
lowest = &Voice
|
||||||
|
} else {
|
||||||
|
for i, mode := range ChannelPrivModes {
|
||||||
|
if strings.Contains(prefixes, ChannelModePrefixes[mode]) {
|
||||||
|
lowest = &ChannelPrivModes[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lowest
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// commands
|
||||||
|
//
|
||||||
|
|
||||||
|
// ParseUserModeChanges returns the valid changes, and the list of unknown chars.
|
||||||
|
func ParseUserModeChanges(params ...string) (ModeChanges, map[rune]bool) {
|
||||||
|
changes := make(ModeChanges, 0)
|
||||||
|
unknown := make(map[rune]bool)
|
||||||
|
|
||||||
|
op := List
|
||||||
|
|
||||||
|
if 0 < len(params) {
|
||||||
|
modeArg := params[0]
|
||||||
|
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 ServerNotice:
|
||||||
|
// always require arg
|
||||||
|
if len(params) > skipArgs {
|
||||||
|
change.Arg = params[skipArgs]
|
||||||
|
skipArgs++
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isKnown bool
|
||||||
|
for _, supportedMode := range SupportedUserModes {
|
||||||
|
if rune(supportedMode) == mode {
|
||||||
|
isKnown = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isKnown {
|
||||||
|
unknown[mode] = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
changes = append(changes, change)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return changes, unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModeSet holds a set of modes.
|
||||||
|
type ModeSet map[Mode]bool
|
||||||
|
|
||||||
|
// String returns the modes in this set.
|
||||||
|
func (set ModeSet) String() string {
|
||||||
|
if len(set) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
strs := make([]string, len(set))
|
||||||
|
index := 0
|
||||||
|
for mode := range set {
|
||||||
|
strs[index] = mode.String()
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
return strings.Join(strs, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefixes returns a list of prefixes for the given set of channel modes.
|
||||||
|
func (set ModeSet) Prefixes(isMultiPrefix bool) string {
|
||||||
|
var prefixes string
|
||||||
|
|
||||||
|
// add prefixes in order from highest to lowest privs
|
||||||
|
for _, mode := range ChannelPrivModes {
|
||||||
|
if set[mode] {
|
||||||
|
prefixes += ChannelModePrefixes[mode]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if set[Voice] {
|
||||||
|
prefixes += ChannelModePrefixes[Voice]
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isMultiPrefix && len(prefixes) > 1 {
|
||||||
|
prefixes = string(prefixes[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
return prefixes
|
||||||
|
}
|
@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/oragono/oragono/irc/caps"
|
"github.com/oragono/oragono/irc/caps"
|
||||||
|
"github.com/oragono/oragono/irc/modes"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -34,7 +35,7 @@ func sendRoleplayMessage(server *Server, client *Client, source string, targetSt
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !channel.flags[ChanRoleplaying] {
|
if !channel.flags[modes.ChanRoleplaying] {
|
||||||
client.Send(nil, client.server.name, ERR_CANNOTSENDRP, channel.name, client.t("Channel doesn't have roleplaying mode available"))
|
client.Send(nil, client.server.name, ERR_CANNOTSENDRP, channel.name, client.t("Channel doesn't have roleplaying mode available"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -53,7 +54,7 @@ func sendRoleplayMessage(server *Server, client *Client, source string, targetSt
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !user.flags[UserRoleplaying] {
|
if !user.flags[modes.UserRoleplaying] {
|
||||||
client.Send(nil, client.server.name, ERR_CANNOTSENDRP, user.nick, client.t("User doesn't have roleplaying mode enabled"))
|
client.Send(nil, client.server.name, ERR_CANNOTSENDRP, user.nick, client.t("User doesn't have roleplaying mode enabled"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -62,7 +63,7 @@ func sendRoleplayMessage(server *Server, client *Client, source string, targetSt
|
|||||||
if client.capabilities.Has(caps.EchoMessage) {
|
if client.capabilities.Has(caps.EchoMessage) {
|
||||||
client.Send(nil, source, "PRIVMSG", user.nick, message)
|
client.Send(nil, source, "PRIVMSG", user.nick, message)
|
||||||
}
|
}
|
||||||
if user.flags[Away] {
|
if user.flags[modes.Away] {
|
||||||
//TODO(dan): possibly implement cooldown of away notifications to users
|
//TODO(dan): possibly implement cooldown of away notifications to users
|
||||||
client.Send(nil, server.name, RPL_AWAY, user.nick, user.awayMessage)
|
client.Send(nil, server.name, RPL_AWAY, user.nick, user.awayMessage)
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ import (
|
|||||||
"github.com/oragono/oragono/irc/isupport"
|
"github.com/oragono/oragono/irc/isupport"
|
||||||
"github.com/oragono/oragono/irc/languages"
|
"github.com/oragono/oragono/irc/languages"
|
||||||
"github.com/oragono/oragono/irc/logger"
|
"github.com/oragono/oragono/irc/logger"
|
||||||
|
"github.com/oragono/oragono/irc/modes"
|
||||||
"github.com/oragono/oragono/irc/passwd"
|
"github.com/oragono/oragono/irc/passwd"
|
||||||
"github.com/oragono/oragono/irc/sno"
|
"github.com/oragono/oragono/irc/sno"
|
||||||
"github.com/oragono/oragono/irc/utils"
|
"github.com/oragono/oragono/irc/utils"
|
||||||
@ -43,6 +44,11 @@ var (
|
|||||||
couldNotParseIPMsg, _ = (&[]ircmsg.IrcMessage{ircmsg.MakeMessage(nil, "", "ERROR", "Unable to parse your IP address")}[0]).Line()
|
couldNotParseIPMsg, _ = (&[]ircmsg.IrcMessage{ircmsg.MakeMessage(nil, "", "ERROR", "Unable to parse your IP address")}[0]).Line()
|
||||||
|
|
||||||
RenamePrivsNeeded = errors.New("Only chanops can rename channels")
|
RenamePrivsNeeded = errors.New("Only chanops can rename channels")
|
||||||
|
|
||||||
|
// supportedUserModesString acts as a cache for when we introduce users
|
||||||
|
supportedUserModesString = modes.SupportedUserModes.String()
|
||||||
|
// supportedChannelModesString acts as a cache for when we introduce users
|
||||||
|
supportedChannelModesString = modes.SupportedChannelModes.String()
|
||||||
)
|
)
|
||||||
|
|
||||||
// Limits holds the maximum limits for various things such as topic lengths.
|
// Limits holds the maximum limits for various things such as topic lengths.
|
||||||
@ -89,7 +95,7 @@ type Server struct {
|
|||||||
connectionLimiter *connection_limits.Limiter
|
connectionLimiter *connection_limits.Limiter
|
||||||
connectionThrottler *connection_limits.Throttler
|
connectionThrottler *connection_limits.Throttler
|
||||||
ctime time.Time
|
ctime time.Time
|
||||||
defaultChannelModes Modes
|
defaultChannelModes modes.Modes
|
||||||
dlines *DLineManager
|
dlines *DLineManager
|
||||||
loggingRawIO bool
|
loggingRawIO bool
|
||||||
isupport *isupport.List
|
isupport *isupport.List
|
||||||
@ -179,7 +185,7 @@ func (server *Server) setISupport() {
|
|||||||
isupport := isupport.NewList()
|
isupport := isupport.NewList()
|
||||||
isupport.Add("AWAYLEN", strconv.Itoa(server.limits.AwayLen))
|
isupport.Add("AWAYLEN", strconv.Itoa(server.limits.AwayLen))
|
||||||
isupport.Add("CASEMAPPING", "ascii")
|
isupport.Add("CASEMAPPING", "ascii")
|
||||||
isupport.Add("CHANMODES", strings.Join([]string{Modes{BanMask, ExceptMask, InviteMask}.String(), "", Modes{UserLimit, Key}.String(), Modes{InviteOnly, Moderated, NoOutside, OpOnlyTopic, ChanRoleplaying, Secret}.String()}, ","))
|
isupport.Add("CHANMODES", strings.Join([]string{modes.Modes{modes.BanMask, modes.ExceptMask, modes.InviteMask}.String(), "", modes.Modes{modes.UserLimit, modes.Key}.String(), modes.Modes{modes.InviteOnly, modes.Moderated, modes.NoOutside, modes.OpOnlyTopic, modes.ChanRoleplaying, modes.Secret}.String()}, ","))
|
||||||
isupport.Add("CHANNELLEN", strconv.Itoa(server.limits.ChannelLen))
|
isupport.Add("CHANNELLEN", strconv.Itoa(server.limits.ChannelLen))
|
||||||
isupport.Add("CHANTYPES", "#")
|
isupport.Add("CHANTYPES", "#")
|
||||||
isupport.Add("ELIST", "U")
|
isupport.Add("ELIST", "U")
|
||||||
@ -224,7 +230,7 @@ func (server *Server) setISupport() {
|
|||||||
server.configurableStateMutex.Unlock()
|
server.configurableStateMutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadChannelList(channel *Channel, list string, maskMode Mode) {
|
func loadChannelList(channel *Channel, list string, maskMode modes.Mode) {
|
||||||
if list == "" {
|
if list == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -584,7 +590,7 @@ func (client *Client) WhoisChannelsNames(target *Client) []string {
|
|||||||
var chstrs []string
|
var chstrs []string
|
||||||
for _, channel := range client.Channels() {
|
for _, channel := range client.Channels() {
|
||||||
// channel is secret and the target can't see it
|
// channel is secret and the target can't see it
|
||||||
if !target.flags[Operator] && channel.HasMode(Secret) && !channel.hasClient(target) {
|
if !target.flags[modes.Operator] && channel.HasMode(modes.Secret) && !channel.hasClient(target) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
chstrs = append(chstrs, channel.ClientPrefixes(client, isMultiPrefix)+channel.name)
|
chstrs = append(chstrs, channel.ClientPrefixes(client, isMultiPrefix)+channel.name)
|
||||||
@ -605,17 +611,17 @@ func (client *Client) getWhoisOf(target *Client) {
|
|||||||
if target.class != nil {
|
if target.class != nil {
|
||||||
client.Send(nil, client.server.name, RPL_WHOISOPERATOR, client.nick, target.nick, target.whoisLine)
|
client.Send(nil, client.server.name, RPL_WHOISOPERATOR, client.nick, target.nick, target.whoisLine)
|
||||||
}
|
}
|
||||||
if client.flags[Operator] || client == target {
|
if client.flags[modes.Operator] || client == target {
|
||||||
client.Send(nil, client.server.name, RPL_WHOISACTUALLY, client.nick, target.nick, fmt.Sprintf("%s@%s", target.username, utils.LookupHostname(target.IPString())), target.IPString(), client.t("Actual user@host, Actual IP"))
|
client.Send(nil, client.server.name, RPL_WHOISACTUALLY, client.nick, target.nick, fmt.Sprintf("%s@%s", target.username, utils.LookupHostname(target.IPString())), target.IPString(), client.t("Actual user@host, Actual IP"))
|
||||||
}
|
}
|
||||||
if target.flags[TLS] {
|
if target.flags[modes.TLS] {
|
||||||
client.Send(nil, client.server.name, RPL_WHOISSECURE, client.nick, target.nick, client.t("is using a secure connection"))
|
client.Send(nil, client.server.name, RPL_WHOISSECURE, client.nick, target.nick, client.t("is using a secure connection"))
|
||||||
}
|
}
|
||||||
accountName := target.AccountName()
|
accountName := target.AccountName()
|
||||||
if accountName != "" {
|
if accountName != "" {
|
||||||
client.Send(nil, client.server.name, RPL_WHOISACCOUNT, client.nick, accountName, client.t("is logged in as"))
|
client.Send(nil, client.server.name, RPL_WHOISACCOUNT, client.nick, accountName, client.t("is logged in as"))
|
||||||
}
|
}
|
||||||
if target.flags[Bot] {
|
if target.flags[modes.Bot] {
|
||||||
client.Send(nil, client.server.name, RPL_WHOISBOT, client.nick, target.nick, ircfmt.Unescape(fmt.Sprintf(client.t("is a $bBot$b on %s"), client.server.networkName)))
|
client.Send(nil, client.server.name, RPL_WHOISBOT, client.nick, target.nick, ircfmt.Unescape(fmt.Sprintf(client.t("is a $bBot$b on %s"), client.server.networkName)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -628,7 +634,7 @@ func (client *Client) getWhoisOf(target *Client) {
|
|||||||
client.Send(nil, client.server.name, RPL_WHOISLANGUAGE, params...)
|
client.Send(nil, client.server.name, RPL_WHOISLANGUAGE, params...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if target.certfp != "" && (client.flags[Operator] || client == target) {
|
if target.certfp != "" && (client.flags[modes.Operator] || client == target) {
|
||||||
client.Send(nil, client.server.name, RPL_WHOISCERTFP, client.nick, target.nick, fmt.Sprintf(client.t("has client certificate fingerprint %s"), target.certfp))
|
client.Send(nil, client.server.name, RPL_WHOISCERTFP, client.nick, target.nick, fmt.Sprintf(client.t("has client certificate fingerprint %s"), target.certfp))
|
||||||
}
|
}
|
||||||
client.Send(nil, client.server.name, RPL_WHOISIDLE, client.nick, target.nick, strconv.FormatUint(target.IdleSeconds(), 10), strconv.FormatInt(target.SignonTime(), 10), client.t("seconds idle, signon time"))
|
client.Send(nil, client.server.name, RPL_WHOISIDLE, client.nick, target.nick, strconv.FormatUint(target.IdleSeconds(), 10), strconv.FormatInt(target.SignonTime(), 10), client.t("seconds idle, signon time"))
|
||||||
@ -641,12 +647,12 @@ func (target *Client) rplWhoReply(channel *Channel, client *Client) {
|
|||||||
channelName := "*"
|
channelName := "*"
|
||||||
flags := ""
|
flags := ""
|
||||||
|
|
||||||
if client.HasMode(Away) {
|
if client.HasMode(modes.Away) {
|
||||||
flags = "G"
|
flags = "G"
|
||||||
} else {
|
} else {
|
||||||
flags = "H"
|
flags = "H"
|
||||||
}
|
}
|
||||||
if client.HasMode(Operator) {
|
if client.HasMode(modes.Operator) {
|
||||||
flags += "*"
|
flags += "*"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -659,7 +665,7 @@ func (target *Client) rplWhoReply(channel *Channel, client *Client) {
|
|||||||
|
|
||||||
func whoChannel(client *Client, channel *Channel, friends ClientSet) {
|
func whoChannel(client *Client, channel *Channel, friends ClientSet) {
|
||||||
for _, member := range channel.Members() {
|
for _, member := range channel.Members() {
|
||||||
if !client.flags[Invisible] || friends[client] {
|
if !client.flags[modes.Invisible] || friends[client] {
|
||||||
client.rplWhoReply(channel, member)
|
client.rplWhoReply(channel, member)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1095,7 +1101,7 @@ func (server *Server) setupListeners(config *Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaultChannelModes returns our default channel modes.
|
// GetDefaultChannelModes returns our default channel modes.
|
||||||
func (server *Server) GetDefaultChannelModes() Modes {
|
func (server *Server) GetDefaultChannelModes() modes.Modes {
|
||||||
server.configurableStateMutex.RLock()
|
server.configurableStateMutex.RLock()
|
||||||
defer server.configurableStateMutex.RUnlock()
|
defer server.configurableStateMutex.RUnlock()
|
||||||
return server.defaultChannelModes
|
return server.defaultChannelModes
|
||||||
@ -1130,11 +1136,11 @@ func (matcher *elistMatcher) Matches(channel *Channel) bool {
|
|||||||
func (target *Client) RplList(channel *Channel) {
|
func (target *Client) RplList(channel *Channel) {
|
||||||
// get the correct number of channel members
|
// get the correct number of channel members
|
||||||
var memberCount int
|
var memberCount int
|
||||||
if target.flags[Operator] || channel.hasClient(target) {
|
if target.flags[modes.Operator] || channel.hasClient(target) {
|
||||||
memberCount = len(channel.Members())
|
memberCount = len(channel.Members())
|
||||||
} else {
|
} else {
|
||||||
for _, member := range channel.Members() {
|
for _, member := range channel.Members() {
|
||||||
if !member.HasMode(Invisible) {
|
if !member.HasMode(modes.Invisible) {
|
||||||
memberCount++
|
memberCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1162,7 +1168,7 @@ func namesHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// limit regular users to only listing one channel
|
// limit regular users to only listing one channel
|
||||||
if !client.flags[Operator] {
|
if !client.flags[modes.Operator] {
|
||||||
channels = channels[:1]
|
channels = channels[:1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
29
irc/types.go
29
irc/types.go
@ -5,26 +5,7 @@
|
|||||||
|
|
||||||
package irc
|
package irc
|
||||||
|
|
||||||
import (
|
import "github.com/oragono/oragono/irc/modes"
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ModeSet holds a set of modes.
|
|
||||||
type ModeSet map[Mode]bool
|
|
||||||
|
|
||||||
// String returns the modes in this set.
|
|
||||||
func (set ModeSet) String() string {
|
|
||||||
if len(set) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
strs := make([]string, len(set))
|
|
||||||
index := 0
|
|
||||||
for mode := range set {
|
|
||||||
strs[index] = mode.String()
|
|
||||||
index++
|
|
||||||
}
|
|
||||||
return strings.Join(strs, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClientSet is a set of clients.
|
// ClientSet is a set of clients.
|
||||||
type ClientSet map[*Client]bool
|
type ClientSet map[*Client]bool
|
||||||
@ -45,11 +26,11 @@ func (clients ClientSet) Has(client *Client) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MemberSet is a set of members with modes.
|
// MemberSet is a set of members with modes.
|
||||||
type MemberSet map[*Client]ModeSet
|
type MemberSet map[*Client]modes.ModeSet
|
||||||
|
|
||||||
// Add adds the given client to this set.
|
// Add adds the given client to this set.
|
||||||
func (members MemberSet) Add(member *Client) {
|
func (members MemberSet) Add(member *Client) {
|
||||||
members[member] = make(ModeSet)
|
members[member] = make(modes.ModeSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove removes the given client from this set.
|
// Remove removes the given client from this set.
|
||||||
@ -64,7 +45,7 @@ func (members MemberSet) Has(member *Client) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HasMode returns true if the given client is in this set with the given mode.
|
// HasMode returns true if the given client is in this set with the given mode.
|
||||||
func (members MemberSet) HasMode(member *Client, mode Mode) bool {
|
func (members MemberSet) HasMode(member *Client, mode modes.Mode) bool {
|
||||||
modes, ok := members[member]
|
modes, ok := members[member]
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
@ -73,7 +54,7 @@ func (members MemberSet) HasMode(member *Client, mode Mode) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AnyHasMode returns true if any of our clients has the given mode.
|
// AnyHasMode returns true if any of our clients has the given mode.
|
||||||
func (members MemberSet) AnyHasMode(mode Mode) bool {
|
func (members MemberSet) AnyHasMode(mode modes.Mode) bool {
|
||||||
for _, modes := range members {
|
for _, modes := range members {
|
||||||
if modes[mode] {
|
if modes[mode] {
|
||||||
return true
|
return true
|
||||||
|
Loading…
x
Reference in New Issue
Block a user