mirror of https://github.com/gissleh/irc.git
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
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()
|
|
}
|