|
|
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 raw map[string]string
prefixes map[rune]rune modeOrder string prefixOrder string chanModes []string }
// 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.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.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.prefixes == nil { return fullnick, "", "" }
for i, ch := range fullnick { if mode, ok := isupport.prefixes[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.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.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.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.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.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.prefixes[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.prefixes { if mappedMode == mode { return prefix } }
return rune(0) }
// Prefixes 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 { isupport.lock.RLock() defer isupport.lock.RUnlock()
return strings.Contains(isupport.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.modeOrder, flag) }
// 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.modeOrder, mode) { return 0 }
for i, block := range isupport.chanModes { 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.raw == nil { isupport.raw = make(map[string]string, 32) }
isupport.raw[key] = value
switch key { case "PREFIX": // PREFIX=(ov)@+
{ split := strings.SplitN(value[1:], ")", 2)
isupport.prefixOrder = split[1] isupport.modeOrder = split[0] isupport.prefixes = make(map[rune]rune, len(split[0])) for i, ch := range split[0] { isupport.prefixes[rune(split[1][i])] = ch } } case "CHANMODES": // CHANMODES=eIbq,k,flj,CFLNPQcgimnprstz
{ isupport.chanModes = strings.Split(value, ",") } }
isupport.lock.Unlock() }
// Reset clears everything.
func (isupport *ISupport) Reset() { isupport.lock.Lock() isupport.prefixOrder = "" isupport.modeOrder = "" isupport.prefixes = nil isupport.chanModes = nil
for key := range isupport.raw { delete(isupport.raw, key) } isupport.lock.Unlock() }
|