Mirror of github.com/gissleh/irc
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

318 lines
7.4 KiB

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()
}