Add MODE arguments support
- Add RPL_ISUPPORT support with CHANMODES, CHANTYPES, PREFIX parsing - Add support for channel mode state with mode arguments - Add upstream support for RPL_UMODEIS, RPL_CHANNELMODEIS - Request channel MODE on upstream channel JOIN - Use sane default channel mode and channel mode types
This commit is contained in:
parent
b0ab43e5d8
commit
98a95e9955
@ -29,10 +29,7 @@ func forwardChannel(dc *downstreamConn, ch *upstreamChannel) {
|
|||||||
|
|
||||||
// TODO: send multiple members in each message
|
// TODO: send multiple members in each message
|
||||||
for nick, membership := range ch.Members {
|
for nick, membership := range ch.Members {
|
||||||
s := dc.marshalNick(ch.conn, nick)
|
s := membership.String() + dc.marshalNick(ch.conn, nick)
|
||||||
if membership != 0 {
|
|
||||||
s = string(membership) + s
|
|
||||||
}
|
|
||||||
|
|
||||||
dc.SendMessage(&irc.Message{
|
dc.SendMessage(&irc.Message{
|
||||||
Prefix: dc.srv.prefix(),
|
Prefix: dc.srv.prefix(),
|
||||||
|
@ -851,41 +851,7 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
|
|||||||
modeStr = msg.Params[1]
|
modeStr = msg.Params[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
uc, upstreamName, err := dc.unmarshalEntity(name)
|
if name == dc.nick {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if uc.isChannel(upstreamName) {
|
|
||||||
// TODO: handle MODE channel mode arguments
|
|
||||||
if modeStr != "" {
|
|
||||||
uc.SendMessage(&irc.Message{
|
|
||||||
Command: "MODE",
|
|
||||||
Params: []string{upstreamName, modeStr},
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
ch, ok := uc.channels[upstreamName]
|
|
||||||
if !ok {
|
|
||||||
return ircError{&irc.Message{
|
|
||||||
Command: irc.ERR_NOSUCHCHANNEL,
|
|
||||||
Params: []string{dc.nick, name, "No such channel"},
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
dc.SendMessage(&irc.Message{
|
|
||||||
Prefix: dc.srv.prefix(),
|
|
||||||
Command: irc.RPL_CHANNELMODEIS,
|
|
||||||
Params: []string{dc.nick, name, string(ch.modes)},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if name != dc.nick {
|
|
||||||
return ircError{&irc.Message{
|
|
||||||
Command: irc.ERR_USERSDONTMATCH,
|
|
||||||
Params: []string{dc.nick, "Cannot change mode for other users"},
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
if modeStr != "" {
|
if modeStr != "" {
|
||||||
dc.forEachUpstream(func(uc *upstreamConn) {
|
dc.forEachUpstream(func(uc *upstreamConn) {
|
||||||
uc.SendMessage(&irc.Message{
|
uc.SendMessage(&irc.Message{
|
||||||
@ -900,6 +866,52 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
|
|||||||
Params: []string{dc.nick, ""}, // TODO
|
Params: []string{dc.nick, ""}, // TODO
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
uc, upstreamName, err := dc.unmarshalEntity(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !uc.isChannel(upstreamName) {
|
||||||
|
return ircError{&irc.Message{
|
||||||
|
Command: irc.ERR_USERSDONTMATCH,
|
||||||
|
Params: []string{dc.nick, "Cannot change mode for other users"},
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
if modeStr != "" {
|
||||||
|
params := []string{upstreamName, modeStr}
|
||||||
|
params = append(params, msg.Params[2:]...)
|
||||||
|
uc.SendMessage(&irc.Message{
|
||||||
|
Command: "MODE",
|
||||||
|
Params: params,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
ch, ok := uc.channels[upstreamName]
|
||||||
|
if !ok {
|
||||||
|
return ircError{&irc.Message{
|
||||||
|
Command: irc.ERR_NOSUCHCHANNEL,
|
||||||
|
Params: []string{dc.nick, name, "No such channel"},
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch.modes == nil {
|
||||||
|
// we haven't received the initial RPL_CHANNELMODEIS yet
|
||||||
|
// ignore the request, we will broadcast the modes later when we receive RPL_CHANNELMODEIS
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
modeStr, modeParams := ch.modes.Format()
|
||||||
|
params := []string{dc.nick, name, modeStr}
|
||||||
|
params = append(params, modeParams...)
|
||||||
|
|
||||||
|
dc.SendMessage(&irc.Message{
|
||||||
|
Prefix: dc.srv.prefix(),
|
||||||
|
Command: irc.RPL_CHANNELMODEIS,
|
||||||
|
Params: params,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
case "WHO":
|
case "WHO":
|
||||||
if len(msg.Params) == 0 {
|
if len(msg.Params) == 0 {
|
||||||
|
138
irc.go
138
irc.go
@ -15,26 +15,26 @@ const (
|
|||||||
err_invalidcapcmd = "410"
|
err_invalidcapcmd = "410"
|
||||||
)
|
)
|
||||||
|
|
||||||
type modeSet string
|
type userModes string
|
||||||
|
|
||||||
func (ms modeSet) Has(c byte) bool {
|
func (ms userModes) Has(c byte) bool {
|
||||||
return strings.IndexByte(string(ms), c) >= 0
|
return strings.IndexByte(string(ms), c) >= 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms *modeSet) Add(c byte) {
|
func (ms *userModes) Add(c byte) {
|
||||||
if !ms.Has(c) {
|
if !ms.Has(c) {
|
||||||
*ms += modeSet(c)
|
*ms += userModes(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms *modeSet) Del(c byte) {
|
func (ms *userModes) Del(c byte) {
|
||||||
i := strings.IndexByte(string(*ms), c)
|
i := strings.IndexByte(string(*ms), c)
|
||||||
if i >= 0 {
|
if i >= 0 {
|
||||||
*ms = (*ms)[:i] + (*ms)[i+1:]
|
*ms = (*ms)[:i] + (*ms)[i+1:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms *modeSet) Apply(s string) error {
|
func (ms *userModes) Apply(s string) error {
|
||||||
var plusMinus byte
|
var plusMinus byte
|
||||||
for i := 0; i < len(s); i++ {
|
for i := 0; i < len(s); i++ {
|
||||||
switch c := s[i]; c {
|
switch c := s[i]; c {
|
||||||
@ -54,6 +54,94 @@ func (ms *modeSet) Apply(s string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type channelModeType byte
|
||||||
|
|
||||||
|
// standard channel mode types, as explained in https://modern.ircdocs.horse/#mode-message
|
||||||
|
const (
|
||||||
|
// modes that add or remove an address to or from a list
|
||||||
|
modeTypeA channelModeType = iota
|
||||||
|
// modes that change a setting on a channel, and must always have a parameter
|
||||||
|
modeTypeB
|
||||||
|
// modes that change a setting on a channel, and must have a parameter when being set, and no parameter when being unset
|
||||||
|
modeTypeC
|
||||||
|
// modes that change a setting on a channel, and must not have a parameter
|
||||||
|
modeTypeD
|
||||||
|
)
|
||||||
|
|
||||||
|
var stdChannelModes = map[byte]channelModeType{
|
||||||
|
'b': modeTypeA, // ban list
|
||||||
|
'e': modeTypeA, // ban exception list
|
||||||
|
'I': modeTypeA, // invite exception list
|
||||||
|
'k': modeTypeB, // channel key
|
||||||
|
'l': modeTypeC, // channel user limit
|
||||||
|
'i': modeTypeD, // channel is invite-only
|
||||||
|
'm': modeTypeD, // channel is moderated
|
||||||
|
'n': modeTypeD, // channel has no external messages
|
||||||
|
's': modeTypeD, // channel is secret
|
||||||
|
't': modeTypeD, // channel has protected topic
|
||||||
|
}
|
||||||
|
|
||||||
|
type channelModes map[byte]string
|
||||||
|
|
||||||
|
func (cm channelModes) Apply(modeTypes map[byte]channelModeType, modeStr string, arguments ...string) error {
|
||||||
|
nextArgument := 0
|
||||||
|
var plusMinus byte
|
||||||
|
for i := 0; i < len(modeStr); i++ {
|
||||||
|
mode := modeStr[i]
|
||||||
|
if mode == '+' || mode == '-' {
|
||||||
|
plusMinus = mode
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if plusMinus != '+' && plusMinus != '-' {
|
||||||
|
return fmt.Errorf("malformed modestring %q: missing plus/minus", modeStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
mt, ok := modeTypes[mode]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if mt == modeTypeB || (mt == modeTypeC && plusMinus == '+') {
|
||||||
|
if plusMinus == '+' {
|
||||||
|
var argument string
|
||||||
|
// some sentitive arguments (such as channel keys) can be omitted for privacy
|
||||||
|
// (this will only happen for RPL_CHANNELMODEIS, never for MODE messages)
|
||||||
|
if nextArgument < len(arguments) {
|
||||||
|
argument = arguments[nextArgument]
|
||||||
|
}
|
||||||
|
cm[mode] = argument
|
||||||
|
} else {
|
||||||
|
delete(cm, mode)
|
||||||
|
}
|
||||||
|
nextArgument++
|
||||||
|
} else if mt == modeTypeC || mt == modeTypeD {
|
||||||
|
if plusMinus == '+' {
|
||||||
|
cm[mode] = ""
|
||||||
|
} else {
|
||||||
|
delete(cm, mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cm channelModes) Format() (modeString string, parameters []string) {
|
||||||
|
var modesWithValues strings.Builder
|
||||||
|
var modesWithoutValues strings.Builder
|
||||||
|
parameters = make([]string, 0, 16)
|
||||||
|
for mode, value := range cm {
|
||||||
|
if value != "" {
|
||||||
|
modesWithValues.WriteString(string(mode))
|
||||||
|
parameters = append(parameters, value)
|
||||||
|
} else {
|
||||||
|
modesWithoutValues.WriteString(string(mode))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
modeString = "+" + modesWithValues.String() + modesWithoutValues.String()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const stdChannelTypes = "#&+!"
|
||||||
|
|
||||||
type channelStatus byte
|
type channelStatus byte
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -74,32 +162,24 @@ func parseChannelStatus(s string) (channelStatus, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type membership byte
|
type membership struct {
|
||||||
|
Mode byte
|
||||||
const (
|
Prefix byte
|
||||||
membershipFounder membership = '~'
|
|
||||||
membershipProtected membership = '&'
|
|
||||||
membershipOperator membership = '@'
|
|
||||||
membershipHalfOp membership = '%'
|
|
||||||
membershipVoice membership = '+'
|
|
||||||
)
|
|
||||||
|
|
||||||
const stdMembershipPrefixes = "~&@%+"
|
|
||||||
|
|
||||||
func (m membership) String() string {
|
|
||||||
if m == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return string(m)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseMembershipPrefix(s string) (prefix membership, nick string) {
|
var stdMemberships = []membership{
|
||||||
// TODO: any prefix from PREFIX RPL_ISUPPORT
|
{'q', '~'}, // founder
|
||||||
if strings.IndexByte(stdMembershipPrefixes, s[0]) >= 0 {
|
{'a', '&'}, // protected
|
||||||
return membership(s[0]), s[1:]
|
{'o', '@'}, // operator
|
||||||
} else {
|
{'h', '%'}, // halfop
|
||||||
return 0, s
|
{'v', '+'}, // voice
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *membership) String() string {
|
||||||
|
if m == nil {
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
return string(m.Prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseMessageParams(msg *irc.Message, out ...*string) error {
|
func parseMessageParams(msg *irc.Message, out ...*string) error {
|
||||||
|
217
upstream.go
217
upstream.go
@ -21,8 +21,8 @@ type upstreamChannel struct {
|
|||||||
TopicWho string
|
TopicWho string
|
||||||
TopicTime time.Time
|
TopicTime time.Time
|
||||||
Status channelStatus
|
Status channelStatus
|
||||||
modes modeSet
|
modes channelModes
|
||||||
Members map[string]membership
|
Members map[string]*membership
|
||||||
complete bool
|
complete bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,15 +38,16 @@ type upstreamConn struct {
|
|||||||
|
|
||||||
serverName string
|
serverName string
|
||||||
availableUserModes string
|
availableUserModes string
|
||||||
availableChannelModes string
|
availableChannelModes map[byte]channelModeType
|
||||||
channelModesWithParam string
|
availableChannelTypes string
|
||||||
|
availableMemberships []membership
|
||||||
|
|
||||||
registered bool
|
registered bool
|
||||||
nick string
|
nick string
|
||||||
username string
|
username string
|
||||||
realname string
|
realname string
|
||||||
closed bool
|
closed bool
|
||||||
modes modeSet
|
modes userModes
|
||||||
channels map[string]*upstreamChannel
|
channels map[string]*upstreamChannel
|
||||||
caps map[string]string
|
caps map[string]string
|
||||||
|
|
||||||
@ -82,6 +83,9 @@ func connectToUpstream(network *network) (*upstreamConn, error) {
|
|||||||
ring: NewRing(network.user.srv.RingCap),
|
ring: NewRing(network.user.srv.RingCap),
|
||||||
channels: make(map[string]*upstreamChannel),
|
channels: make(map[string]*upstreamChannel),
|
||||||
caps: make(map[string]string),
|
caps: make(map[string]string),
|
||||||
|
availableChannelTypes: stdChannelTypes,
|
||||||
|
availableChannelModes: stdChannelModes,
|
||||||
|
availableMemberships: stdMemberships,
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@ -130,17 +134,21 @@ func (uc *upstreamConn) getChannel(name string) (*upstreamChannel, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (uc *upstreamConn) isChannel(entity string) bool {
|
func (uc *upstreamConn) isChannel(entity string) bool {
|
||||||
for _, r := range entity {
|
if i := strings.IndexByte(uc.availableChannelTypes, entity[0]); i >= 0 {
|
||||||
switch r {
|
|
||||||
// TODO: support upstream ISUPPORT channel prefixes
|
|
||||||
case '#', '&', '+', '!':
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
break
|
|
||||||
}
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (uc *upstreamConn) parseMembershipPrefix(s string) (membership *membership, nick string) {
|
||||||
|
for _, m := range uc.availableMemberships {
|
||||||
|
if m.Prefix == s[0] {
|
||||||
|
return &m, s[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, s
|
||||||
|
}
|
||||||
|
|
||||||
func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
|
func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
|
||||||
switch msg.Command {
|
switch msg.Command {
|
||||||
case "PING":
|
case "PING":
|
||||||
@ -149,35 +157,6 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
|
|||||||
Params: msg.Params,
|
Params: msg.Params,
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
case "MODE":
|
|
||||||
var name, modeStr string
|
|
||||||
if err := parseMessageParams(msg, &name, &modeStr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !uc.isChannel(name) { // user mode change
|
|
||||||
if name != uc.nick {
|
|
||||||
return fmt.Errorf("received MODE message for unknown nick %q", name)
|
|
||||||
}
|
|
||||||
return uc.modes.Apply(modeStr)
|
|
||||||
} else { // channel mode change
|
|
||||||
// TODO: handle MODE channel mode arguments
|
|
||||||
ch, err := uc.getChannel(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := ch.modes.Apply(modeStr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
uc.forEachDownstream(func(dc *downstreamConn) {
|
|
||||||
dc.SendMessage(&irc.Message{
|
|
||||||
Prefix: dc.marshalUserPrefix(uc, msg.Prefix),
|
|
||||||
Command: "MODE",
|
|
||||||
Params: []string{dc.marshalChannel(uc, name), modeStr},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
case "NOTICE":
|
case "NOTICE":
|
||||||
uc.logger.Print(msg)
|
uc.logger.Print(msg)
|
||||||
|
|
||||||
@ -346,11 +325,67 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
case irc.RPL_MYINFO:
|
case irc.RPL_MYINFO:
|
||||||
if err := parseMessageParams(msg, nil, &uc.serverName, nil, &uc.availableUserModes, &uc.availableChannelModes); err != nil {
|
if err := parseMessageParams(msg, nil, &uc.serverName, nil, &uc.availableUserModes, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(msg.Params) > 5 {
|
case irc.RPL_ISUPPORT:
|
||||||
uc.channelModesWithParam = msg.Params[5]
|
if err := parseMessageParams(msg, nil, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, token := range msg.Params[1 : len(msg.Params)-1] {
|
||||||
|
negate := false
|
||||||
|
parameter := token
|
||||||
|
value := ""
|
||||||
|
if strings.HasPrefix(token, "-") {
|
||||||
|
negate = true
|
||||||
|
token = token[1:]
|
||||||
|
} else {
|
||||||
|
if i := strings.IndexByte(token, '='); i >= 0 {
|
||||||
|
parameter = token[:i]
|
||||||
|
value = token[i+1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !negate {
|
||||||
|
switch parameter {
|
||||||
|
case "CHANMODES":
|
||||||
|
parts := strings.SplitN(value, ",", 5)
|
||||||
|
if len(parts) < 4 {
|
||||||
|
return fmt.Errorf("malformed ISUPPORT CHANMODES value: %v", value)
|
||||||
|
}
|
||||||
|
modes := make(map[byte]channelModeType)
|
||||||
|
for i, mt := range []channelModeType{modeTypeA, modeTypeB, modeTypeC, modeTypeD} {
|
||||||
|
for j := 0; j < len(parts[i]); j++ {
|
||||||
|
mode := parts[i][j]
|
||||||
|
modes[mode] = mt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uc.availableChannelModes = modes
|
||||||
|
case "CHANTYPES":
|
||||||
|
uc.availableChannelTypes = value
|
||||||
|
case "PREFIX":
|
||||||
|
if value == "" {
|
||||||
|
uc.availableMemberships = nil
|
||||||
|
} else {
|
||||||
|
if value[0] != '(' {
|
||||||
|
return fmt.Errorf("malformed ISUPPORT PREFIX value: %v", value)
|
||||||
|
}
|
||||||
|
sep := strings.IndexByte(value, ')')
|
||||||
|
if sep < 0 || len(value) != sep*2 {
|
||||||
|
return fmt.Errorf("malformed ISUPPORT PREFIX value: %v", value)
|
||||||
|
}
|
||||||
|
memberships := make([]membership, len(value)/2-1)
|
||||||
|
for i := range memberships {
|
||||||
|
memberships[i] = membership{
|
||||||
|
Mode: value[i+1],
|
||||||
|
Prefix: value[sep+i+1],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uc.availableMemberships = memberships
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: handle ISUPPORT negations
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case "NICK":
|
case "NICK":
|
||||||
if msg.Prefix == nil {
|
if msg.Prefix == nil {
|
||||||
@ -399,14 +434,19 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
|
|||||||
uc.channels[ch] = &upstreamChannel{
|
uc.channels[ch] = &upstreamChannel{
|
||||||
Name: ch,
|
Name: ch,
|
||||||
conn: uc,
|
conn: uc,
|
||||||
Members: make(map[string]membership),
|
Members: make(map[string]*membership),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uc.SendMessage(&irc.Message{
|
||||||
|
Command: "MODE",
|
||||||
|
Params: []string{ch},
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
ch, err := uc.getChannel(ch)
|
ch, err := uc.getChannel(ch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ch.Members[msg.Prefix.Name] = 0
|
ch.Members[msg.Prefix.Name] = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
uc.forEachDownstream(func(dc *downstreamConn) {
|
uc.forEachDownstream(func(dc *downstreamConn) {
|
||||||
@ -508,6 +548,89 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
|
|||||||
Params: params,
|
Params: params,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
case "MODE":
|
||||||
|
var name, modeStr string
|
||||||
|
if err := parseMessageParams(msg, &name, &modeStr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !uc.isChannel(name) { // user mode change
|
||||||
|
if name != uc.nick {
|
||||||
|
return fmt.Errorf("received MODE message for unknown nick %q", name)
|
||||||
|
}
|
||||||
|
return uc.modes.Apply(modeStr)
|
||||||
|
// TODO: notify downstreams about user mode change?
|
||||||
|
} else { // channel mode change
|
||||||
|
ch, err := uc.getChannel(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch.modes != nil {
|
||||||
|
if err := ch.modes.Apply(uc.availableChannelModes, modeStr, msg.Params[2:]...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uc.forEachDownstream(func(dc *downstreamConn) {
|
||||||
|
params := []string{dc.marshalChannel(uc, name), modeStr}
|
||||||
|
params = append(params, msg.Params[2:]...)
|
||||||
|
|
||||||
|
dc.SendMessage(&irc.Message{
|
||||||
|
Prefix: dc.marshalUserPrefix(uc, msg.Prefix),
|
||||||
|
Command: "MODE",
|
||||||
|
Params: params,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case irc.RPL_UMODEIS:
|
||||||
|
if err := parseMessageParams(msg, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
modeStr := ""
|
||||||
|
if len(msg.Params) > 1 {
|
||||||
|
modeStr = msg.Params[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
uc.modes = ""
|
||||||
|
if err := uc.modes.Apply(modeStr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// TODO: send RPL_UMODEIS to downstream connections when applicable
|
||||||
|
case irc.RPL_CHANNELMODEIS:
|
||||||
|
var channel string
|
||||||
|
if err := parseMessageParams(msg, nil, &channel); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
modeStr := ""
|
||||||
|
if len(msg.Params) > 2 {
|
||||||
|
modeStr = msg.Params[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
ch, err := uc.getChannel(channel)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
firstMode := ch.modes == nil
|
||||||
|
ch.modes = make(map[byte]string)
|
||||||
|
if err := ch.modes.Apply(uc.availableChannelModes, modeStr, msg.Params[3:]...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if firstMode {
|
||||||
|
modeStr, modeParams := ch.modes.Format()
|
||||||
|
|
||||||
|
uc.forEachDownstream(func(dc *downstreamConn) {
|
||||||
|
params := []string{dc.nick, dc.marshalChannel(uc, channel), modeStr}
|
||||||
|
params = append(params, modeParams...)
|
||||||
|
|
||||||
|
dc.SendMessage(&irc.Message{
|
||||||
|
Prefix: dc.srv.prefix(),
|
||||||
|
Command: irc.RPL_CHANNELMODEIS,
|
||||||
|
Params: params,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
case rpl_topicwhotime:
|
case rpl_topicwhotime:
|
||||||
var name, who, timeStr string
|
var name, who, timeStr string
|
||||||
if err := parseMessageParams(msg, nil, &name, &who, &timeStr); err != nil {
|
if err := parseMessageParams(msg, nil, &name, &who, &timeStr); err != nil {
|
||||||
@ -540,7 +663,7 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
|
|||||||
ch.Status = status
|
ch.Status = status
|
||||||
|
|
||||||
for _, s := range strings.Split(members, " ") {
|
for _, s := range strings.Split(members, " ") {
|
||||||
membership, nick := parseMembershipPrefix(s)
|
membership, nick := uc.parseMembershipPrefix(s)
|
||||||
ch.Members[nick] = membership
|
ch.Members[nick] = membership
|
||||||
}
|
}
|
||||||
case irc.RPL_ENDOFNAMES:
|
case irc.RPL_ENDOFNAMES:
|
||||||
@ -679,7 +802,7 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
|
|||||||
nick := dc.marshalNick(uc, nick)
|
nick := dc.marshalNick(uc, nick)
|
||||||
channelList := make([]string, len(channels))
|
channelList := make([]string, len(channels))
|
||||||
for i, channel := range channels {
|
for i, channel := range channels {
|
||||||
prefix, channel := parseMembershipPrefix(channel)
|
prefix, channel := uc.parseMembershipPrefix(channel)
|
||||||
channel = dc.marshalChannel(uc, channel)
|
channel = dc.marshalChannel(uc, channel)
|
||||||
channelList[i] = prefix.String() + channel
|
channelList[i] = prefix.String() + channel
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user