|
|
package isupport
import ( "strconv" "strings" "sync" )
// ISupport is a data structure containing server instructions about
// supported modes, encodings, lengths, prefixes, and so on. It is built
// from the 005 numeric's data, and has helper methods that makes sense
// of it. It's thread-safe through a reader/writer lock, so the locks will
// only block in the short duration post-registration when the 005s come in
type ISupport struct { lock sync.RWMutex state State }
// Get gets an isupport key. This is unprocessed data, and a helper should
// be used if available.
func (isupport *ISupport) Get(key string) (value string, ok bool) { isupport.lock.RLock() value, ok = isupport.state.Raw[key] isupport.lock.RUnlock() return }
// Number gets a key and converts it to a number.
func (isupport *ISupport) Number(key string) (value int, ok bool) { isupport.lock.RLock() strValue, ok := isupport.state.Raw[key] isupport.lock.RUnlock()
if !ok { return 0, ok }
value, err := strconv.Atoi(strValue) if err != nil { return value, false }
return value, ok }
// ParsePrefixedNick parses a full nick into its components.
// Example: "@+HammerTime62" -> `"HammerTime62", "ov", "@+"`
func (isupport *ISupport) ParsePrefixedNick(fullnick string) (nick, modes, prefixes string) { isupport.lock.RLock() defer isupport.lock.RUnlock()
if fullnick == "" || isupport.state.PrefixMap == nil { return fullnick, "", "" }
for i, ch := range fullnick { if mode, ok := isupport.state.PrefixMap[ch]; ok { modes += string(mode) prefixes += string(ch) } else { nick = fullnick[i:] break } }
return nick, modes, prefixes }
// HighestPrefix gets the highest-level prefix declared by PREFIX
func (isupport *ISupport) HighestPrefix(prefixes string) rune { isupport.lock.RLock() defer isupport.lock.RUnlock()
if len(prefixes) == 1 { return rune(prefixes[0]) }
for _, prefix := range isupport.state.PrefixOrder { if strings.ContainsRune(prefixes, prefix) { return prefix } }
return rune(0) }
// HighestMode gets the highest-level mode declared by PREFIX
func (isupport *ISupport) HighestMode(modes string) rune { isupport.lock.RLock() defer isupport.lock.RUnlock()
if len(modes) == 1 { return rune(modes[0]) }
for _, mode := range isupport.state.ModeOrder { if strings.ContainsRune(modes, mode) { return mode } }
return rune(0) }
// IsModeHigher returns true if `current` is a higher mode than `other`.
func (isupport *ISupport) IsModeHigher(current rune, other rune) bool { isupport.lock.RLock() defer isupport.lock.RUnlock()
if current == other { return false } if current == 0 { return false } if other == 0 { return true }
for _, mode := range isupport.state.ModeOrder { if mode == current { return true } else if mode == other { return false } }
return false }
// SortModes returns the modes in order. Any unknown modes will be omitted.
func (isupport *ISupport) SortModes(modes string) string { result := ""
for _, ch := range isupport.state.ModeOrder { for _, ch2 := range modes { if ch2 == ch { result += string(ch) } } }
return result }
// SortPrefixes returns the prefixes in order. Any unknown prefixes will be omitted.
func (isupport *ISupport) SortPrefixes(prefixes string) string { result := ""
for _, ch := range isupport.state.PrefixOrder { for _, ch2 := range prefixes { if ch2 == ch { result += string(ch) } } }
return result }
// Mode gets the mode for the prefix.
func (isupport *ISupport) Mode(prefix rune) rune { isupport.lock.RLock() defer isupport.lock.RUnlock()
return isupport.state.PrefixMap[prefix] }
// Prefix gets the prefix for the mode. It's a bit slower
// than the other way around, but is a far less frequently
// used.
func (isupport *ISupport) Prefix(mode rune) rune { isupport.lock.RLock() defer isupport.lock.RUnlock()
for prefix, mappedMode := range isupport.state.PrefixMap { if mappedMode == mode { return prefix } }
return rune(0) }
// PrefixMap gets the prefixes in the order of the modes, skipping any
// invalid modes.
func (isupport *ISupport) Prefixes(modes string) string { result := ""
for _, mode := range modes { prefix := isupport.Prefix(mode) if prefix != mode { result += string(prefix) } }
return result }
// IsChannel returns whether the target name is a channel.
func (isupport *ISupport) IsChannel(targetName string) bool { if len(targetName) < 1 { return false }
isupport.lock.RLock() defer isupport.lock.RUnlock()
return strings.Contains(isupport.state.Raw["CHANTYPES"], string(targetName[0])) }
// IsPermissionMode returns whether the flag is a permission mode
func (isupport *ISupport) IsPermissionMode(flag rune) bool { isupport.lock.RLock() defer isupport.lock.RUnlock()
return strings.ContainsRune(isupport.state.ModeOrder, flag) }
// ModeTakesArgument returns true if the mode takes an argument
func (isupport *ISupport) ModeTakesArgument(flag rune, plus bool) bool { isupport.lock.RLock() defer isupport.lock.RUnlock()
// Permission modes always take an argument.
if strings.ContainsRune(isupport.state.ModeOrder, flag) { return true }
// Modes in category A and B always takes an argument
if strings.ContainsRune(isupport.state.ChannelModes[0], flag) || strings.ContainsRune(isupport.state.ChannelModes[1], flag) { return true }
// Modes in category C only takes one when added
if plus && strings.ContainsRune(isupport.state.ChannelModes[1], flag) { return true }
// Modes in category D and outside never does
return false }
// ChannelModeType returns a number from 0 to 3 based on what block of mode
// in the CHANMODES variable it fits into. If it's not found at all, it will
// return -1
func (isupport *ISupport) ChannelModeType(mode rune) int { isupport.lock.RLock() defer isupport.lock.RUnlock()
// User permission modes function exactly like the first block
// when it comes to add/remove
if strings.ContainsRune(isupport.state.ModeOrder, mode) { return 0 }
for i, block := range isupport.state.ChannelModes { if strings.ContainsRune(block, mode) { return i } }
return -1 }
// Set sets an isupport key, and related structs. This should only be used
// if a 005 packet contains the Key-Value pair or if it can be "polyfilled"
// in some other way.
func (isupport *ISupport) Set(key, value string) { key = strings.ToUpper(key)
isupport.lock.Lock()
if isupport.state.Raw == nil { isupport.state.Raw = make(map[string]string, 32) }
isupport.state.Raw[key] = value
switch key { case "PREFIX": // PREFIX=(ov)@+
{ split := strings.SplitN(value[1:], ")", 2)
isupport.state.PrefixOrder = split[1] isupport.state.ModeOrder = split[0] isupport.state.PrefixMap = make(map[rune]rune, len(split[0])) for i, ch := range split[0] { isupport.state.PrefixMap[rune(split[1][i])] = ch } } case "CHANMODES": // CHANMODES=eIbq,k,flj,CFLNPQcgimnprstz
{ isupport.state.ChannelModes = strings.Split(value, ",") } }
isupport.lock.Unlock() }
// State gets a copy of the isupport state.
func (isupport *ISupport) State() *State { return isupport.state.Copy() }
// Reset clears everything.
func (isupport *ISupport) Reset() { isupport.lock.Lock() isupport.state.PrefixOrder = "" isupport.state.ModeOrder = "" isupport.state.PrefixMap = nil isupport.state.ChannelModes = nil
for key := range isupport.state.Raw { delete(isupport.state.Raw, key) } isupport.lock.Unlock() }
|