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.
331 lines
7.3 KiB
331 lines
7.3 KiB
package list
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/gissleh/irc/isupport"
|
|
)
|
|
|
|
// The List of users in a channel. It has all operations one would perform on
|
|
// users, like adding/removing modes and changing nicks.
|
|
type List struct {
|
|
mutex sync.RWMutex
|
|
isupport *isupport.ISupport
|
|
users []*User
|
|
index map[string]*User
|
|
autosort bool
|
|
}
|
|
|
|
// New creates a new list with the ISupport. The list can be reused between connections since the
|
|
// ISupport is simply cleared and repopulated, but it should be cleared.
|
|
func New(isupport *isupport.ISupport) *List {
|
|
return &List{
|
|
isupport: isupport,
|
|
users: make([]*User, 0, 64),
|
|
index: make(map[string]*User, 64),
|
|
autosort: true,
|
|
}
|
|
}
|
|
|
|
// InsertFromNamesToken inserts using a NAMES token to get the nick, user, host and prefixes.
|
|
// The format is `"@+Nick@user!hostmask.example.com"`
|
|
func (list *List) InsertFromNamesToken(namestoken string) (ok bool) {
|
|
user := User{}
|
|
|
|
// Parse prefixes and modes. @ and ! (It's IRCHighWay if you were wondering) are both
|
|
// mode prefixes and that just makes a mess if leave them for last. It also supports
|
|
// `multi-prefix`
|
|
for i, ch := range namestoken {
|
|
mode := list.isupport.Mode(ch)
|
|
if mode == 0 {
|
|
if i != 0 {
|
|
namestoken = namestoken[i:]
|
|
}
|
|
break
|
|
}
|
|
|
|
user.Prefixes += string(ch)
|
|
user.Modes += string(mode)
|
|
}
|
|
|
|
// Get the nick
|
|
split := strings.Split(namestoken, "!")
|
|
user.Nick = split[0]
|
|
|
|
// Support `userhost-in-names`
|
|
if len(split) == 2 {
|
|
userhost := strings.Split(split[1], "@")
|
|
if len(userhost) == 2 {
|
|
user.User = userhost[0]
|
|
user.Host = userhost[1]
|
|
}
|
|
}
|
|
|
|
ok = list.Insert(user)
|
|
if !ok {
|
|
// Patch the user's modes and prefixes since this is up to date information.
|
|
list.mutex.Lock()
|
|
for _, existing := range list.users {
|
|
if existing.Nick == user.Nick {
|
|
existing.Modes = user.Modes
|
|
existing.Prefixes = user.Prefixes
|
|
existing.updatePrefixedNick()
|
|
break
|
|
}
|
|
}
|
|
list.mutex.Unlock()
|
|
}
|
|
|
|
return ok
|
|
}
|
|
|
|
// Insert a user. Modes and prefixes will be cleaned up before insertion.
|
|
func (list *List) Insert(user User) (ok bool) {
|
|
if len(user.Modes) > 0 {
|
|
// IRCv3 promises they'll be ordered by rank in WHO and NAMES replies,
|
|
// but one can never be too sure with IRC.
|
|
user.Modes = list.isupport.SortModes(user.Modes)
|
|
if len(user.Prefixes) < len(user.Modes) {
|
|
user.Prefixes = list.isupport.Prefixes(user.Modes)
|
|
} else {
|
|
user.Prefixes = list.isupport.SortPrefixes(user.Prefixes)
|
|
}
|
|
user.updatePrefixedNick()
|
|
} else {
|
|
user.Prefixes = ""
|
|
user.updatePrefixedNick()
|
|
}
|
|
|
|
list.mutex.Lock()
|
|
defer list.mutex.Unlock()
|
|
|
|
if list.index[strings.ToLower(user.Nick)] != nil {
|
|
return false
|
|
}
|
|
|
|
list.users = append(list.users, &user)
|
|
list.index[strings.ToLower(user.Nick)] = &user
|
|
|
|
if list.autosort {
|
|
list.sort()
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// AddMode adds a mode to a user. Redundant modes will be ignored. It returns true if
|
|
// the user can be found, even if the mode was redundant.
|
|
func (list *List) AddMode(nick string, mode rune) (ok bool) {
|
|
if !list.isupport.IsPermissionMode(mode) {
|
|
return false
|
|
}
|
|
|
|
list.mutex.RLock()
|
|
defer list.mutex.RUnlock()
|
|
|
|
user := list.index[strings.ToLower(nick)]
|
|
if user == nil {
|
|
return false
|
|
}
|
|
if strings.ContainsRune(user.Modes, mode) {
|
|
return true
|
|
}
|
|
|
|
prevHighest := user.HighestMode()
|
|
user.Modes = list.isupport.SortModes(user.Modes + string(mode))
|
|
user.Prefixes = list.isupport.Prefixes(user.Modes)
|
|
user.updatePrefixedNick()
|
|
|
|
// Only sort if the new mode changed the highest mode.
|
|
if list.autosort && prevHighest != user.HighestMode() {
|
|
list.sort()
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// RemoveMode adds a mode to a user. It returns true if
|
|
// the user can be found, even if the mode was not there.
|
|
func (list *List) RemoveMode(nick string, mode rune) (ok bool) {
|
|
if !list.isupport.IsPermissionMode(mode) {
|
|
return false
|
|
}
|
|
|
|
list.mutex.RLock()
|
|
defer list.mutex.RUnlock()
|
|
|
|
user := list.index[strings.ToLower(nick)]
|
|
if user == nil {
|
|
return false
|
|
}
|
|
if !strings.ContainsRune(user.Modes, mode) {
|
|
return true
|
|
}
|
|
|
|
prevHighest := user.HighestMode()
|
|
user.Modes = strings.Replace(user.Modes, string(mode), "", 1)
|
|
user.Prefixes = strings.Replace(user.Prefixes, string(list.isupport.Prefix(mode)), "", 1)
|
|
user.updatePrefixedNick()
|
|
|
|
// Only sort if the new mode changed the highest mode.
|
|
if list.autosort && prevHighest != user.HighestMode() {
|
|
list.sort()
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Rename renames a user. It will return true if user by `from` exists, or if user by `to` does not exist.
|
|
func (list *List) Rename(from, to string) (ok bool) {
|
|
fromKey := strings.ToLower(from)
|
|
toKey := strings.ToLower(to)
|
|
|
|
list.mutex.Lock()
|
|
defer list.mutex.Unlock()
|
|
|
|
// Sanitiy check
|
|
user := list.index[fromKey]
|
|
if user == nil {
|
|
return false
|
|
}
|
|
if from == to {
|
|
return true
|
|
}
|
|
existing := list.index[toKey]
|
|
if existing != nil {
|
|
return false
|
|
}
|
|
|
|
user.Nick = to
|
|
user.updatePrefixedNick()
|
|
|
|
delete(list.index, fromKey)
|
|
list.index[toKey] = user
|
|
|
|
if list.autosort {
|
|
list.sort()
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Remove a user from the userlist.
|
|
func (list *List) Remove(nick string) (ok bool) {
|
|
list.mutex.Lock()
|
|
defer list.mutex.Unlock()
|
|
|
|
user := list.index[strings.ToLower(nick)]
|
|
if user == nil {
|
|
return false
|
|
}
|
|
|
|
for i := range list.users {
|
|
if list.users[i] == user {
|
|
list.users = append(list.users[:i], list.users[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
delete(list.index, strings.ToLower(nick))
|
|
|
|
return true
|
|
}
|
|
|
|
// User gets a copy of the user by nick, or an empty user if there is none.
|
|
func (list *List) User(nick string) (u User, ok bool) {
|
|
list.mutex.RLock()
|
|
defer list.mutex.RUnlock()
|
|
|
|
user := list.index[strings.ToLower(nick)]
|
|
if user == nil {
|
|
return User{}, false
|
|
}
|
|
|
|
return *user, true
|
|
}
|
|
|
|
// Users gets a copy of the users in the list's current state.
|
|
func (list *List) Users() []User {
|
|
result := make([]User, len(list.users))
|
|
list.mutex.RLock()
|
|
for i := range list.users {
|
|
result[i] = *list.users[i]
|
|
}
|
|
list.mutex.RUnlock()
|
|
|
|
return result
|
|
}
|
|
|
|
// Patch allows editing a limited subset of the user's properties.
|
|
func (list *List) Patch(nick string, patch UserPatch) (ok bool) {
|
|
list.mutex.Lock()
|
|
defer list.mutex.Unlock()
|
|
|
|
for _, user := range list.users {
|
|
if strings.EqualFold(nick, user.Nick) {
|
|
if patch.Account != "" || patch.ClearAccount {
|
|
user.Account = patch.Account
|
|
}
|
|
|
|
if patch.Away != "" || patch.ClearAway {
|
|
user.Away = patch.Away
|
|
}
|
|
|
|
if patch.User != "" {
|
|
user.User = patch.User
|
|
}
|
|
|
|
if patch.Host != "" {
|
|
user.Host = patch.Host
|
|
}
|
|
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// SetAutoSort enables or disables automatic sorting, which by default is enabled.
|
|
// Dislabing it makes sense when doing a massive operation. Enabling it will trigger
|
|
// a sort.
|
|
func (list *List) SetAutoSort(autosort bool) {
|
|
list.mutex.Lock()
|
|
list.autosort = autosort
|
|
list.sort()
|
|
list.mutex.Unlock()
|
|
}
|
|
|
|
// Clear removes all users in a list.
|
|
func (list *List) Clear() {
|
|
list.mutex.Lock()
|
|
|
|
list.users = list.users[:0]
|
|
for key := range list.index {
|
|
delete(list.index, key)
|
|
}
|
|
|
|
list.mutex.Unlock()
|
|
}
|
|
|
|
// Immutable gets an immutable version of the list.
|
|
func (list *List) Immutable() Immutable {
|
|
return Immutable{list: list}
|
|
}
|
|
|
|
func (list *List) sort() {
|
|
sort.Slice(list.users, func(i, j int) bool {
|
|
a := list.users[i]
|
|
b := list.users[j]
|
|
|
|
aMode := a.HighestMode()
|
|
bMode := b.HighestMode()
|
|
|
|
if aMode != bMode {
|
|
return list.isupport.IsModeHigher(aMode, bMode)
|
|
}
|
|
|
|
return strings.ToLower(a.Nick) < strings.ToLower(b.Nick)
|
|
})
|
|
}
|