Changeset 139 in code for trunk/irc.go


Ignore:
Timestamp:
Mar 25, 2020, 8:40:08 AM (5 years ago)
Author:
delthas
Message:

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
File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/irc.go

    r128 r139  
    1616)
    1717
    18 type modeSet string
     18type userModes string
    1919
    20 func (ms modeSet) Has(c byte) bool {
     20func (ms userModes) Has(c byte) bool {
    2121        return strings.IndexByte(string(ms), c) >= 0
    2222}
    2323
    24 func (ms *modeSet) Add(c byte) {
     24func (ms *userModes) Add(c byte) {
    2525        if !ms.Has(c) {
    26                 *ms += modeSet(c)
     26                *ms += userModes(c)
    2727        }
    2828}
    2929
    30 func (ms *modeSet) Del(c byte) {
     30func (ms *userModes) Del(c byte) {
    3131        i := strings.IndexByte(string(*ms), c)
    3232        if i >= 0 {
     
    3535}
    3636
    37 func (ms *modeSet) Apply(s string) error {
     37func (ms *userModes) Apply(s string) error {
    3838        var plusMinus byte
    3939        for i := 0; i < len(s); i++ {
     
    5454        return nil
    5555}
     56
     57type channelModeType byte
     58
     59// standard channel mode types, as explained in https://modern.ircdocs.horse/#mode-message
     60const (
     61        // modes that add or remove an address to or from a list
     62        modeTypeA channelModeType = iota
     63        // modes that change a setting on a channel, and must always have a parameter
     64        modeTypeB
     65        // modes that change a setting on a channel, and must have a parameter when being set, and no parameter when being unset
     66        modeTypeC
     67        // modes that change a setting on a channel, and must not have a parameter
     68        modeTypeD
     69)
     70
     71var stdChannelModes = map[byte]channelModeType{
     72        'b': modeTypeA, // ban list
     73        'e': modeTypeA, // ban exception list
     74        'I': modeTypeA, // invite exception list
     75        'k': modeTypeB, // channel key
     76        'l': modeTypeC, // channel user limit
     77        'i': modeTypeD, // channel is invite-only
     78        'm': modeTypeD, // channel is moderated
     79        'n': modeTypeD, // channel has no external messages
     80        's': modeTypeD, // channel is secret
     81        't': modeTypeD, // channel has protected topic
     82}
     83
     84type channelModes map[byte]string
     85
     86func (cm channelModes) Apply(modeTypes map[byte]channelModeType, modeStr string, arguments ...string) error {
     87        nextArgument := 0
     88        var plusMinus byte
     89        for i := 0; i < len(modeStr); i++ {
     90                mode := modeStr[i]
     91                if mode == '+' || mode == '-' {
     92                        plusMinus = mode
     93                        continue
     94                }
     95                if plusMinus != '+' && plusMinus != '-' {
     96                        return fmt.Errorf("malformed modestring %q: missing plus/minus", modeStr)
     97                }
     98
     99                mt, ok := modeTypes[mode]
     100                if !ok {
     101                        continue
     102                }
     103                if mt == modeTypeB || (mt == modeTypeC && plusMinus == '+') {
     104                        if plusMinus == '+' {
     105                                var argument string
     106                                // some sentitive arguments (such as channel keys) can be omitted for privacy
     107                                // (this will only happen for RPL_CHANNELMODEIS, never for MODE messages)
     108                                if nextArgument < len(arguments) {
     109                                        argument = arguments[nextArgument]
     110                                }
     111                                cm[mode] = argument
     112                        } else {
     113                                delete(cm, mode)
     114                        }
     115                        nextArgument++
     116                } else if mt == modeTypeC || mt == modeTypeD {
     117                        if plusMinus == '+' {
     118                                cm[mode] = ""
     119                        } else {
     120                                delete(cm, mode)
     121                        }
     122                }
     123        }
     124        return nil
     125}
     126
     127func (cm channelModes) Format() (modeString string, parameters []string) {
     128        var modesWithValues strings.Builder
     129        var modesWithoutValues strings.Builder
     130        parameters = make([]string, 0, 16)
     131        for mode, value := range cm {
     132                if value != "" {
     133                        modesWithValues.WriteString(string(mode))
     134                        parameters = append(parameters, value)
     135                } else {
     136                        modesWithoutValues.WriteString(string(mode))
     137                }
     138        }
     139        modeString = "+" + modesWithValues.String() + modesWithoutValues.String()
     140        return
     141}
     142
     143const stdChannelTypes = "#&+!"
    56144
    57145type channelStatus byte
     
    75163}
    76164
    77 type membership byte
     165type membership struct {
     166        Mode   byte
     167        Prefix byte
     168}
    78169
    79 const (
    80         membershipFounder   membership = '~'
    81         membershipProtected membership = '&'
    82         membershipOperator  membership = '@'
    83         membershipHalfOp    membership = '%'
    84         membershipVoice     membership = '+'
    85 )
     170var stdMemberships = []membership{
     171        {'q', '~'}, // founder
     172        {'a', '&'}, // protected
     173        {'o', '@'}, // operator
     174        {'h', '%'}, // halfop
     175        {'v', '+'}, // voice
     176}
    86177
    87 const stdMembershipPrefixes = "~&@%+"
    88 
    89 func (m membership) String() string {
    90         if m == 0 {
     178func (m *membership) String() string {
     179        if m == nil {
    91180                return ""
    92181        }
    93         return string(m)
    94 }
    95 
    96 func parseMembershipPrefix(s string) (prefix membership, nick string) {
    97         // TODO: any prefix from PREFIX RPL_ISUPPORT
    98         if strings.IndexByte(stdMembershipPrefixes, s[0]) >= 0 {
    99                 return membership(s[0]), s[1:]
    100         } else {
    101                 return 0, s
    102         }
     182        return string(m.Prefix)
    103183}
    104184
Note: See TracChangeset for help on using the changeset viewer.