1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
|
package irc
import (
"errors"
"strings"
"sync"
)
// ISupportTracker tracks the ISUPPORT values returned by servers and provides a
// convenient way to access them.
//
// From http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt
//
// 005 RPL_ISUPPORT.
type ISupportTracker struct {
sync.RWMutex
data map[string]string
}
// NewISupportTracker creates a new tracker instance with a set of sane defaults
// if the server is missing them.
func NewISupportTracker() *ISupportTracker {
return &ISupportTracker{
data: map[string]string{
"PREFIX": "(ov)@+",
},
}
}
// Handle needs to be called for all 005 IRC messages. All other messages will
// be ignored.
func (t *ISupportTracker) Handle(msg *Message) error {
// Ensure only ISupport messages go through here
if msg.Command != "005" {
return nil
}
if len(msg.Params) < 2 {
return errors.New("malformed RPL_ISUPPORT message")
}
// Check for really old servers (or servers which based 005 off of rfc2812).
if !strings.HasSuffix(msg.Trailing(), "server") {
return errors.New("received invalid RPL_ISUPPORT message")
}
t.Lock()
defer t.Unlock()
for _, param := range msg.Params[1 : len(msg.Params)-1] {
data := strings.SplitN(param, "=", 2)
if len(data) < 2 {
t.data[data[0]] = ""
continue
}
// TODO: this should properly handle decoding values containing \xHH
t.data[data[0]] = data[1]
}
return nil
}
// IsEnabled will check for boolean ISupport values. Note that for ISupport
// boolean true simply means the value exists.
func (t *ISupportTracker) IsEnabled(key string) bool {
t.RLock()
defer t.RUnlock()
_, ok := t.data[key]
return ok
}
// GetList will check for list ISupport values.
func (t *ISupportTracker) GetList(key string) ([]string, bool) {
t.RLock()
defer t.RUnlock()
data, ok := t.data[key]
if !ok {
return nil, false
}
return strings.Split(data, ","), true
}
// GetMap will check for map ISupport values.
func (t *ISupportTracker) GetMap(key string) (map[string]string, bool) {
t.RLock()
defer t.RUnlock()
data, ok := t.data[key]
if !ok {
return nil, false
}
ret := make(map[string]string)
for _, v := range strings.Split(data, ",") {
innerData := strings.SplitN(v, ":", 2)
if len(innerData) != 2 {
return nil, false
}
ret[innerData[0]] = innerData[1]
}
return ret, true
}
// GetRaw will get the raw ISupport values.
func (t *ISupportTracker) GetRaw(key string) (string, bool) {
t.RLock()
defer t.RUnlock()
ret, ok := t.data[key]
return ret, ok
}
// GetPrefixMap gets the mapping of mode to symbol for the PREFIX value.
// Unfortunately, this is fairly specific, so it can only be used with PREFIX.
func (t *ISupportTracker) GetPrefixMap() (map[rune]rune, bool) {
// Sample: (qaohv)~&@%+
prefix, _ := t.GetRaw("PREFIX")
// We only care about the symbols
i := strings.IndexByte(prefix, ')')
if len(prefix) == 0 || prefix[0] != '(' || i < 0 {
// "Invalid prefix format"
return nil, false
}
// We loop through the string using range so we get bytes, then we throw the
// two results together in the map.
symbols := make([]rune, 0, len(prefix)/2-1) // ~&@%+
for _, r := range prefix[i+1:] {
symbols = append(symbols, r)
}
modes := make([]rune, 0, len(symbols)) // qaohv
for _, r := range prefix[1:i] {
modes = append(modes, r)
}
if len(modes) != len(symbols) {
// "Mismatched modes and symbols"
return nil, false
}
prefixes := make(map[rune]rune)
for k := range symbols {
prefixes[symbols[k]] = modes[k]
}
return prefixes, true
}
|