diff options
Diffstat (limited to 'vendor/github.com/lrstanley/girc/state.go')
-rw-r--r-- | vendor/github.com/lrstanley/girc/state.go | 489 |
1 files changed, 489 insertions, 0 deletions
diff --git a/vendor/github.com/lrstanley/girc/state.go b/vendor/github.com/lrstanley/girc/state.go new file mode 100644 index 00000000..7c537028 --- /dev/null +++ b/vendor/github.com/lrstanley/girc/state.go @@ -0,0 +1,489 @@ +// Copyright (c) Liam Stanley <me@liamstanley.io>. All rights reserved. Use +// of this source code is governed by the MIT license that can be found in +// the LICENSE file. + +package girc + +import ( + "sort" + "sync" + "time" +) + +// state represents the actively-changing variables within the client +// runtime. Note that everything within the state should be guarded by the +// embedded sync.RWMutex. +type state struct { + sync.RWMutex + // nick, ident, and host are the internal trackers for our user. + nick, ident, host string + // channels represents all channels we're active in. + channels map[string]*Channel + // users represents all of users that we're tracking. + users map[string]*User + // enabledCap are the capabilities which are enabled for this connection. + enabledCap []string + // tmpCap are the capabilties which we share with the server during the + // last capability check. These will get sent once we have received the + // last capability list command from the server. + tmpCap []string + // serverOptions are the standard capabilities and configurations + // supported by the server at connection time. This also includes + // RPL_ISUPPORT entries. + serverOptions map[string]string + // motd is the servers message of the day. + motd string +} + +// notify sends state change notifications so users can update their refs +// when state changes. +func (s *state) notify(c *Client, ntype string) { + c.RunHandlers(&Event{Command: ntype}) +} + +// reset resets the state back to it's original form. +func (s *state) reset() { + s.Lock() + s.nick = "" + s.ident = "" + s.host = "" + s.channels = make(map[string]*Channel) + s.users = make(map[string]*User) + s.serverOptions = make(map[string]string) + s.enabledCap = []string{} + s.motd = "" + s.Unlock() +} + +// User represents an IRC user and the state attached to them. +type User struct { + // Nick is the users current nickname. rfc1459 compliant. + Nick string `json:"nick"` + // Ident is the users username/ident. Ident is commonly prefixed with a + // "~", which indicates that they do not have a identd server setup for + // authentication. + Ident string `json:"ident"` + // Host is the visible host of the users connection that the server has + // provided to us for their connection. May not always be accurate due to + // many networks spoofing/hiding parts of the hostname for privacy + // reasons. + Host string `json:"host"` + + // ChannelList is a sorted list of all channels that we are currently + // tracking the user in. Each channel name is rfc1459 compliant. See + // User.Channels() for a shorthand if you're looking for the *Channel + // version of the channel list. + ChannelList []string `json:"channels"` + + // FirstSeen represents the first time that the user was seen by the + // client for the given channel. Only usable if from state, not in past. + FirstSeen time.Time `json:"first_seen"` + // LastActive represents the last time that we saw the user active, + // which could be during nickname change, message, channel join, etc. + // Only usable if from state, not in past. + LastActive time.Time `json:"last_active"` + + // Perms are the user permissions applied to this user that affect the given + // channel. This supports non-rfc style modes like Admin, Owner, and HalfOp. + Perms *UserPerms `json:"perms"` + + // Extras are things added on by additional tracking methods, which may + // or may not work on the IRC server in mention. + Extras struct { + // Name is the users "realname" or full name. Commonly contains links + // to the IRC client being used, or something of non-importance. May + // also be empty if unsupported by the server/tracking is disabled. + Name string `json:"name"` + // Account refers to the account which the user is authenticated as. + // This differs between each network (e.g. usually Nickserv, but + // could also be something like Undernet). May also be empty if + // unsupported by the server/tracking is disabled. + Account string `json:"account"` + // Away refers to the away status of the user. An empty string + // indicates that they are active, otherwise the string is what they + // set as their away message. May also be empty if unsupported by the + // server/tracking is disabled. + Away string `json:"away"` + } `json:"extras"` +} + +// Channels returns a reference of *Channels that the client knows the user +// is in. If you're just looking for the namme of the channels, use +// User.ChannelList. +func (u User) Channels(c *Client) []*Channel { + if c == nil { + panic("nil Client provided") + } + + channels := []*Channel{} + + c.state.RLock() + for i := 0; i < len(u.ChannelList); i++ { + ch := c.state.lookupChannel(u.ChannelList[i]) + if ch != nil { + channels = append(channels, ch) + } + } + c.state.RUnlock() + + return channels +} + +// Copy returns a deep copy of the user which can be modified without making +// changes to the actual state. +func (u *User) Copy() *User { + nu := &User{} + *nu = *u + + nu.Perms = u.Perms.Copy() + _ = copy(nu.ChannelList, u.ChannelList) + + return nu +} + +// addChannel adds the channel to the users channel list. +func (u *User) addChannel(name string) { + if u.InChannel(name) { + return + } + + u.ChannelList = append(u.ChannelList, ToRFC1459(name)) + sort.StringsAreSorted(u.ChannelList) + + u.Perms.set(name, Perms{}) +} + +// deleteChannel removes an existing channel from the users channel list. +func (u *User) deleteChannel(name string) { + name = ToRFC1459(name) + + j := -1 + for i := 0; i < len(u.ChannelList); i++ { + if u.ChannelList[i] == name { + j = i + break + } + } + + if j != -1 { + u.ChannelList = append(u.ChannelList[:j], u.ChannelList[j+1:]...) + } + + u.Perms.remove(name) +} + +// InChannel checks to see if a user is in the given channel. +func (u *User) InChannel(name string) bool { + name = ToRFC1459(name) + + for i := 0; i < len(u.ChannelList); i++ { + if u.ChannelList[i] == name { + return true + } + } + + return false +} + +// Lifetime represents the amount of time that has passed since we have first +// seen the user. +func (u *User) Lifetime() time.Duration { + return time.Since(u.FirstSeen) +} + +// Active represents the the amount of time that has passed since we have +// last seen the user. +func (u *User) Active() time.Duration { + return time.Since(u.LastActive) +} + +// IsActive returns true if they were active within the last 30 minutes. +func (u *User) IsActive() bool { + return u.Active() < (time.Minute * 30) +} + +// Channel represents an IRC channel and the state attached to it. +type Channel struct { + // Name of the channel. Must be rfc1459 compliant. + Name string `json:"name"` + // Topic of the channel. + Topic string `json:"topic"` + + // UserList is a sorted list of all users we are currently tracking within + // the channel. Each is the nickname, and is rfc1459 compliant. + UserList []string `json:"user_list"` + // Joined represents the first time that the client joined the channel. + Joined time.Time `json:"joined"` + // Modes are the known channel modes that the bot has captured. + Modes CModes `json:"modes"` +} + +// Users returns a reference of *Users that the client knows the channel has +// If you're just looking for just the name of the users, use Channnel.UserList. +func (ch Channel) Users(c *Client) []*User { + if c == nil { + panic("nil Client provided") + } + + users := []*User{} + + c.state.RLock() + for i := 0; i < len(ch.UserList); i++ { + user := c.state.lookupUser(ch.UserList[i]) + if user != nil { + users = append(users, user) + } + } + c.state.RUnlock() + + return users +} + +// Trusted returns a list of users which have voice or greater in the given +// channel. See Perms.IsTrusted() for more information. +func (ch Channel) Trusted(c *Client) []*User { + if c == nil { + panic("nil Client provided") + } + + users := []*User{} + + c.state.RLock() + for i := 0; i < len(ch.UserList); i++ { + user := c.state.lookupUser(ch.UserList[i]) + if user == nil { + continue + } + + perms, ok := user.Perms.Lookup(ch.Name) + if ok && perms.IsTrusted() { + users = append(users, user) + } + } + c.state.RUnlock() + + return users +} + +// Admins returns a list of users which have half-op (if supported), or +// greater permissions (op, admin, owner, etc) in the given channel. See +// Perms.IsAdmin() for more information. +func (ch Channel) Admins(c *Client) []*User { + if c == nil { + panic("nil Client provided") + } + + users := []*User{} + + c.state.RLock() + for i := 0; i < len(ch.UserList); i++ { + user := c.state.lookupUser(ch.UserList[i]) + if user == nil { + continue + } + + perms, ok := user.Perms.Lookup(ch.Name) + if ok && perms.IsAdmin() { + users = append(users, user) + } + } + c.state.RUnlock() + + return users +} + +// addUser adds a user to the users list. +func (ch *Channel) addUser(nick string) { + if ch.UserIn(nick) { + return + } + + ch.UserList = append(ch.UserList, ToRFC1459(nick)) + sort.Strings(ch.UserList) +} + +// deleteUser removes an existing user from the users list. +func (ch *Channel) deleteUser(nick string) { + nick = ToRFC1459(nick) + + j := -1 + for i := 0; i < len(ch.UserList); i++ { + if ch.UserList[i] == nick { + j = i + break + } + } + + if j != -1 { + ch.UserList = append(ch.UserList[:j], ch.UserList[j+1:]...) + } +} + +// Copy returns a deep copy of a given channel. +func (ch *Channel) Copy() *Channel { + nc := &Channel{} + *nc = *ch + + _ = copy(nc.UserList, ch.UserList) + + // And modes. + nc.Modes = ch.Modes.Copy() + + return nc +} + +// Len returns the count of users in a given channel. +func (ch *Channel) Len() int { + return len(ch.UserList) +} + +// UserIn checks to see if a given user is in a channel. +func (ch *Channel) UserIn(name string) bool { + name = ToRFC1459(name) + + for i := 0; i < len(ch.UserList); i++ { + if ch.UserList[i] == name { + return true + } + } + + return false +} + +// Lifetime represents the amount of time that has passed since we have first +// joined the channel. +func (ch *Channel) Lifetime() time.Duration { + return time.Since(ch.Joined) +} + +// createChannel creates the channel in state, if not already done. +func (s *state) createChannel(name string) (ok bool) { + supported := s.chanModes() + prefixes, _ := parsePrefixes(s.userPrefixes()) + + if _, ok := s.channels[ToRFC1459(name)]; ok { + return false + } + + s.channels[ToRFC1459(name)] = &Channel{ + Name: name, + UserList: []string{}, + Joined: time.Now(), + Modes: NewCModes(supported, prefixes), + } + + return true +} + +// deleteChannel removes the channel from state, if not already done. +func (s *state) deleteChannel(name string) { + name = ToRFC1459(name) + + _, ok := s.channels[name] + if !ok { + return + } + + for _, user := range s.channels[name].UserList { + s.users[user].deleteChannel(name) + + if len(s.users[user].ChannelList) == 0 { + // Assume we were only tracking them in this channel, and they + // should be removed from state. + + delete(s.users, user) + } + } + + delete(s.channels, name) +} + +// lookupChannel returns a reference to a channel, nil returned if no results +// found. +func (s *state) lookupChannel(name string) *Channel { + return s.channels[ToRFC1459(name)] +} + +// lookupUser returns a reference to a user, nil returned if no results +// found. +func (s *state) lookupUser(name string) *User { + return s.users[ToRFC1459(name)] +} + +// createUser creates the user in state, if not already done. +func (s *state) createUser(nick string) (ok bool) { + if _, ok := s.users[ToRFC1459(nick)]; ok { + // User already exists. + return false + } + + s.users[ToRFC1459(nick)] = &User{ + Nick: nick, + FirstSeen: time.Now(), + LastActive: time.Now(), + Perms: &UserPerms{channels: make(map[string]Perms)}, + } + + return true +} + +// deleteUser removes the user from channel state. +func (s *state) deleteUser(channelName, nick string) { + user := s.lookupUser(nick) + if user == nil { + return + } + + if channelName == "" { + for i := 0; i < len(user.ChannelList); i++ { + s.channels[user.ChannelList[i]].deleteUser(nick) + } + + delete(s.users, ToRFC1459(nick)) + return + } + + channel := s.lookupChannel(channelName) + if channel == nil { + return + } + + user.deleteChannel(channelName) + channel.deleteUser(nick) + + if len(user.ChannelList) == 0 { + // This means they are no longer in any channels we track, delete + // them from state. + + delete(s.users, ToRFC1459(nick)) + } +} + +// renameUser renames the user in state, in all locations where relevant. +func (s *state) renameUser(from, to string) { + from = ToRFC1459(from) + + // Update our nickname. + if from == ToRFC1459(s.nick) { + s.nick = to + } + + user := s.lookupUser(from) + if user == nil { + return + } + + delete(s.users, from) + + user.Nick = to + user.LastActive = time.Now() + s.users[ToRFC1459(to)] = user + + for i := 0; i < len(user.ChannelList); i++ { + for j := 0; j < len(s.channels[user.ChannelList[i]].UserList); j++ { + if s.channels[user.ChannelList[i]].UserList[j] == from { + s.channels[user.ChannelList[i]].UserList[j] = ToRFC1459(to) + } + } + } +} |