rewrite state to support separate dataset for users
This commit is contained in:
parent
4c8bd8a350
commit
1f66c9ffec
35
builtin.go
35
builtin.go
@ -140,6 +140,7 @@ func handleJOIN(c *Client, e Event) {
|
|||||||
|
|
||||||
// Assume extended-join (ircv3).
|
// Assume extended-join (ircv3).
|
||||||
if len(e.Params) == 2 {
|
if len(e.Params) == 2 {
|
||||||
|
c.state.mu.Lock()
|
||||||
if e.Params[1] != "*" {
|
if e.Params[1] != "*" {
|
||||||
user.Extras.Account = e.Params[1]
|
user.Extras.Account = e.Params[1]
|
||||||
}
|
}
|
||||||
@ -147,6 +148,7 @@ func handleJOIN(c *Client, e Event) {
|
|||||||
if len(e.Trailing) > 0 {
|
if len(e.Trailing) > 0 {
|
||||||
user.Extras.Name = e.Trailing
|
user.Extras.Name = e.Trailing
|
||||||
}
|
}
|
||||||
|
c.state.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Source.Name == c.GetNick() {
|
if e.Source.Name == c.GetNick() {
|
||||||
@ -176,19 +178,26 @@ func handlePART(c *Client, e Event) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(e.Params) == 0 {
|
var channel string
|
||||||
|
if len(e.Params) > 0 {
|
||||||
|
channel = e.Params[0]
|
||||||
|
} else {
|
||||||
|
channel = e.Trailing
|
||||||
|
}
|
||||||
|
|
||||||
|
if channel == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Source.Name == c.GetNick() {
|
if e.Source.Name == c.GetNick() {
|
||||||
c.state.mu.Lock()
|
c.state.mu.Lock()
|
||||||
c.state.deleteChannel(e.Params[0])
|
c.state.deleteChannel(channel)
|
||||||
c.state.mu.Unlock()
|
c.state.mu.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.state.mu.Lock()
|
c.state.mu.Lock()
|
||||||
c.state.deleteUser(e.Source.Name)
|
c.state.deleteUser(channel, e.Source.Name)
|
||||||
c.state.mu.Unlock()
|
c.state.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,6 +245,7 @@ func handleWHO(c *Client, e Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
channel, ident, host, nick, account = e.Params[2], e.Params[3], e.Params[4], e.Params[5], e.Params[6]
|
channel, ident, host, nick, account = e.Params[2], e.Params[3], e.Params[4], e.Params[5], e.Params[6]
|
||||||
|
realname = e.Trailing
|
||||||
} else {
|
} else {
|
||||||
// Assume RPL_WHOREPLY.
|
// Assume RPL_WHOREPLY.
|
||||||
channel, ident, host, nick = e.Params[1], e.Params[2], e.Params[3], e.Params[5]
|
channel, ident, host, nick = e.Params[1], e.Params[2], e.Params[3], e.Params[5]
|
||||||
@ -279,7 +289,7 @@ func handleKICK(c *Client, e Event) {
|
|||||||
|
|
||||||
// Assume it's just another user.
|
// Assume it's just another user.
|
||||||
c.state.mu.Lock()
|
c.state.mu.Lock()
|
||||||
c.state.deleteUser(e.Params[1])
|
c.state.deleteUser(e.Params[0], e.Params[1])
|
||||||
c.state.mu.Unlock()
|
c.state.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,8 +316,12 @@ func handleQUIT(c *Client, e Event) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if e.Source.Name == c.GetNick() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.state.mu.Lock()
|
c.state.mu.Lock()
|
||||||
c.state.deleteUser(e.Source.Name)
|
c.state.deleteUser("", e.Source.Name)
|
||||||
c.state.mu.Unlock()
|
c.state.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -450,10 +464,13 @@ func updateLastActive(c *Client, e Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.state.mu.Lock()
|
c.state.mu.Lock()
|
||||||
|
defer c.state.mu.Unlock()
|
||||||
|
|
||||||
// Update the users last active time, if they exist.
|
// Update the users last active time, if they exist.
|
||||||
users := c.state.lookupUsers("nick", e.Source.Name)
|
user := c.state.lookupUser(e.Source.Name)
|
||||||
for i := 0; i < len(users); i++ {
|
if user == nil {
|
||||||
users[i].LastActive = time.Now()
|
return
|
||||||
}
|
}
|
||||||
c.state.mu.Unlock()
|
|
||||||
|
user.LastActive = time.Now()
|
||||||
}
|
}
|
||||||
|
30
cap.go
30
cap.go
@ -321,11 +321,10 @@ func handleCHGHOST(c *Client, e Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.state.mu.Lock()
|
c.state.mu.Lock()
|
||||||
users := c.state.lookupUsers("nick", e.Source.Name)
|
user := c.state.lookupUser(e.Source.Name)
|
||||||
|
if user != nil {
|
||||||
for i := 0; i < len(users); i++ {
|
user.Ident = e.Params[0]
|
||||||
users[i].Ident = e.Params[0]
|
user.Host = e.Params[1]
|
||||||
users[i].Host = e.Params[1]
|
|
||||||
}
|
}
|
||||||
c.state.mu.Unlock()
|
c.state.mu.Unlock()
|
||||||
}
|
}
|
||||||
@ -334,10 +333,9 @@ func handleCHGHOST(c *Client, e Event) {
|
|||||||
// when users are no longer away, or when they are away.
|
// when users are no longer away, or when they are away.
|
||||||
func handleAWAY(c *Client, e Event) {
|
func handleAWAY(c *Client, e Event) {
|
||||||
c.state.mu.Lock()
|
c.state.mu.Lock()
|
||||||
users := c.state.lookupUsers("nick", e.Source.Name)
|
user := c.state.lookupUser(e.Source.Name)
|
||||||
|
if user != nil {
|
||||||
for i := 0; i < len(users); i++ {
|
user.Extras.Away = e.Trailing
|
||||||
users[i].Extras.Away = e.Trailing
|
|
||||||
}
|
}
|
||||||
c.state.mu.Unlock()
|
c.state.mu.Unlock()
|
||||||
}
|
}
|
||||||
@ -357,10 +355,9 @@ func handleACCOUNT(c *Client, e Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.state.mu.Lock()
|
c.state.mu.Lock()
|
||||||
users := c.state.lookupUsers("nick", e.Source.Name)
|
user := c.state.lookupUser(e.Source.Name)
|
||||||
|
if user != nil {
|
||||||
for i := 0; i < len(users); i++ {
|
user.Extras.Account = account
|
||||||
users[i].Extras.Account = account
|
|
||||||
}
|
}
|
||||||
c.state.mu.Unlock()
|
c.state.mu.Unlock()
|
||||||
}
|
}
|
||||||
@ -378,10 +375,9 @@ func handleTags(c *Client, e Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.state.mu.Lock()
|
c.state.mu.Lock()
|
||||||
users := c.state.lookupUsers("nick", e.Source.Name)
|
user := c.state.lookupUser(e.Source.Name)
|
||||||
|
if user != nil {
|
||||||
for i := 0; i < len(users); i++ {
|
user.Extras.Account = account
|
||||||
users[i].Extras.Account = account
|
|
||||||
}
|
}
|
||||||
c.state.mu.Unlock()
|
c.state.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
58
client.go
58
client.go
@ -13,7 +13,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -237,7 +237,7 @@ func New(config Config) *Client {
|
|||||||
|
|
||||||
// Give ourselves a new state.
|
// Give ourselves a new state.
|
||||||
c.state = &state{}
|
c.state = &state{}
|
||||||
c.state.clean()
|
c.state.reset()
|
||||||
|
|
||||||
// Register builtin handlers.
|
// Register builtin handlers.
|
||||||
c.registerBuiltins()
|
c.registerBuiltins()
|
||||||
@ -430,27 +430,50 @@ func (c *Client) GetHost() string {
|
|||||||
// Panics if tracking is disabled.
|
// Panics if tracking is disabled.
|
||||||
func (c *Client) Channels() []string {
|
func (c *Client) Channels() []string {
|
||||||
c.panicIfNotTracking()
|
c.panicIfNotTracking()
|
||||||
channels := make([]string, len(c.state.channels))
|
|
||||||
|
|
||||||
c.state.mu.RLock()
|
c.state.mu.RLock()
|
||||||
|
channels := make([]string, len(c.state.channels))
|
||||||
var i int
|
var i int
|
||||||
for channel := range c.state.channels {
|
for channel := range c.state.channels {
|
||||||
channels[i] = channel
|
channels[i] = channel
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
c.state.mu.RUnlock()
|
c.state.mu.RUnlock()
|
||||||
|
sort.Strings(channels)
|
||||||
|
|
||||||
return channels
|
return channels
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lookup looks up a given channel in state. If the channel doesn't exist,
|
// Users returns the active list of users that the client is tracking across
|
||||||
// channel is nil. Panics if tracking is disabled.
|
// all files. Panics if tracking is disabled.
|
||||||
func (c *Client) Lookup(name string) *Channel {
|
func (c *Client) Users() []string {
|
||||||
c.panicIfNotTracking()
|
c.panicIfNotTracking()
|
||||||
|
|
||||||
|
c.state.mu.RLock()
|
||||||
|
users := make([]string, len(c.state.users))
|
||||||
|
var i int
|
||||||
|
for user := range c.state.users {
|
||||||
|
users[i] = user
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
c.state.mu.RUnlock()
|
||||||
|
sort.Strings(users)
|
||||||
|
|
||||||
|
return users
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupChannel looks up a given channel in state. If the channel doesn't
|
||||||
|
// exist, nil is returned. Panics if tracking is disabled.
|
||||||
|
func (c *Client) LookupChannel(name string) *Channel {
|
||||||
|
c.panicIfNotTracking()
|
||||||
|
if name == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
c.state.mu.Lock()
|
c.state.mu.Lock()
|
||||||
defer c.state.mu.Unlock()
|
|
||||||
|
|
||||||
channel := c.state.lookupChannel(name)
|
channel := c.state.lookupChannel(name)
|
||||||
|
c.state.mu.Unlock()
|
||||||
if channel == nil {
|
if channel == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -458,13 +481,32 @@ func (c *Client) Lookup(name string) *Channel {
|
|||||||
return channel.Copy()
|
return channel.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LookupUser looks up a given user in state. If the user doesn't exist, nil
|
||||||
|
// is returned. Panics if tracking is disabled.
|
||||||
|
func (c *Client) LookupUser(nick string) *User {
|
||||||
|
c.panicIfNotTracking()
|
||||||
|
if nick == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.state.mu.Lock()
|
||||||
|
|
||||||
|
user := c.state.lookupUser(nick)
|
||||||
|
c.state.mu.Unlock()
|
||||||
|
if user == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.Copy()
|
||||||
|
}
|
||||||
|
|
||||||
// IsInChannel returns true if the client is in channel. Panics if tracking
|
// IsInChannel returns true if the client is in channel. Panics if tracking
|
||||||
// is disabled.
|
// is disabled.
|
||||||
func (c *Client) IsInChannel(channel string) bool {
|
func (c *Client) IsInChannel(channel string) bool {
|
||||||
c.panicIfNotTracking()
|
c.panicIfNotTracking()
|
||||||
|
|
||||||
c.state.mu.RLock()
|
c.state.mu.RLock()
|
||||||
_, inChannel := c.state.channels[strings.ToLower(channel)]
|
_, inChannel := c.state.channels[ToRFC1459(channel)]
|
||||||
c.state.mu.RUnlock()
|
c.state.mu.RUnlock()
|
||||||
|
|
||||||
return inChannel
|
return inChannel
|
||||||
|
2
conn.go
2
conn.go
@ -265,7 +265,7 @@ func (c *Client) internalConnect(mock net.Conn) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reset the state.
|
// Reset the state.
|
||||||
c.state.clean()
|
c.state.reset()
|
||||||
|
|
||||||
if mock == nil {
|
if mock == nil {
|
||||||
// Validate info, and actually make the connection.
|
// Validate info, and actually make the connection.
|
||||||
|
9
event.go
9
event.go
@ -394,7 +394,7 @@ func (e *Event) GetChannel(c *Client) *Channel {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Lookup(e.Params[0])
|
return c.LookupChannel(e.Params[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUser is a helper function around an event which lets you easily obtain
|
// GetUser is a helper function around an event which lets you easily obtain
|
||||||
@ -411,12 +411,7 @@ func (e *Event) GetUser(c *Client) *User {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
channel := c.Lookup(e.Params[0])
|
return c.LookupUser(e.Source.Name)
|
||||||
if channel == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return channel.Lookup(e.Source.Name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAction checks to see if the event is a PRIVMSG, and is an ACTION (/me).
|
// IsAction checks to see if the event is a PRIVMSG, and is an ACTION (/me).
|
||||||
|
@ -239,7 +239,7 @@ func IsValidUser(name string) bool {
|
|||||||
|
|
||||||
// ToRFC1459 converts a string to the stripped down conversion within RFC
|
// ToRFC1459 converts a string to the stripped down conversion within RFC
|
||||||
// 1459. This will do things like replace an "A" with an "a", "[]" with "{}",
|
// 1459. This will do things like replace an "A" with an "a", "[]" with "{}",
|
||||||
// and so forth. Useful to compare two nicknames.
|
// and so forth. Useful to compare two nicknames or channels.
|
||||||
func ToRFC1459(input string) (out string) {
|
func ToRFC1459(input string) (out string) {
|
||||||
for i := 0; i < len(input); i++ {
|
for i := 0; i < len(input); i++ {
|
||||||
if input[i] >= 65 && input[i] <= 94 {
|
if input[i] >= 65 && input[i] <= 94 {
|
||||||
|
6
modes.go
6
modes.go
@ -351,9 +351,9 @@ func handleMODE(c *Client, e Event) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
users := c.state.lookupUsers("nick", modes[i].args)
|
user := c.state.lookupUser(modes[i].args)
|
||||||
for j := 0; j < len(users); j++ {
|
if user != nil {
|
||||||
users[j].Perms.setFromMode(modes[i])
|
user.Perms.setFromMode(modes[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
305
state.go
305
state.go
@ -5,7 +5,7 @@
|
|||||||
package girc
|
package girc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -20,6 +20,8 @@ type state struct {
|
|||||||
nick, ident, host string
|
nick, ident, host string
|
||||||
// channels represents all channels we're active in.
|
// channels represents all channels we're active in.
|
||||||
channels map[string]*Channel
|
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 are the capabilities which are enabled for this connection.
|
||||||
enabledCap []string
|
enabledCap []string
|
||||||
// tmpCap are the capabilties which we share with the server during the
|
// tmpCap are the capabilties which we share with the server during the
|
||||||
@ -34,12 +36,14 @@ type state struct {
|
|||||||
motd string
|
motd string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *state) clean() {
|
// reset resets the state back to it's original form.
|
||||||
|
func (s *state) reset() {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
s.nick = ""
|
s.nick = ""
|
||||||
s.ident = ""
|
s.ident = ""
|
||||||
s.host = ""
|
s.host = ""
|
||||||
s.channels = make(map[string]*Channel)
|
s.channels = make(map[string]*Channel)
|
||||||
|
s.users = make(map[string]*User)
|
||||||
s.serverOptions = make(map[string]string)
|
s.serverOptions = make(map[string]string)
|
||||||
s.enabledCap = []string{}
|
s.enabledCap = []string{}
|
||||||
s.motd = ""
|
s.motd = ""
|
||||||
@ -48,7 +52,7 @@ func (s *state) clean() {
|
|||||||
|
|
||||||
// User represents an IRC user and the state attached to them.
|
// User represents an IRC user and the state attached to them.
|
||||||
type User struct {
|
type User struct {
|
||||||
// Nick is the users current nickname.
|
// Nick is the users current nickname. rfc1459 compliant.
|
||||||
Nick string
|
Nick string
|
||||||
// Ident is the users username/ident. Ident is commonly prefixed with a
|
// Ident is the users username/ident. Ident is commonly prefixed with a
|
||||||
// "~", which indicates that they do not have a identd server setup for
|
// "~", which indicates that they do not have a identd server setup for
|
||||||
@ -60,6 +64,10 @@ type User struct {
|
|||||||
// reasons.
|
// reasons.
|
||||||
Host string
|
Host string
|
||||||
|
|
||||||
|
// Channels is a sorted list of all channels that we are currently tracking
|
||||||
|
// the user in. Each channel name is rfc1459 compliant.
|
||||||
|
Channels []string
|
||||||
|
|
||||||
// FirstSeen represents the first time that the user was seen by the
|
// 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.
|
// client for the given channel. Only usable if from state, not in past.
|
||||||
FirstSeen time.Time
|
FirstSeen time.Time
|
||||||
@ -94,6 +102,46 @@ type User struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
_ = copy(nu.Channels, u.Channels)
|
||||||
|
|
||||||
|
return nu
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) deleteChannel(name string) {
|
||||||
|
name = ToRFC1459(name)
|
||||||
|
|
||||||
|
j := -1
|
||||||
|
for i := 0; i < len(u.Channels); i++ {
|
||||||
|
if u.Channels[i] == name {
|
||||||
|
j = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if j != -1 {
|
||||||
|
u.Channels = append(u.Channels[:j], u.Channels[j+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.Channels); i++ {
|
||||||
|
if u.Channels[i] == name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Lifetime represents the amount of time that has passed since we have first
|
// Lifetime represents the amount of time that has passed since we have first
|
||||||
// seen the user.
|
// seen the user.
|
||||||
func (u *User) Lifetime() time.Duration {
|
func (u *User) Lifetime() time.Duration {
|
||||||
@ -113,30 +161,42 @@ func (u *User) IsActive() bool {
|
|||||||
|
|
||||||
// Channel represents an IRC channel and the state attached to it.
|
// Channel represents an IRC channel and the state attached to it.
|
||||||
type Channel struct {
|
type Channel struct {
|
||||||
// Name of the channel. Must be rfc compliant. Always represented as
|
// Name of the channel. Must be rfc1459 compliant.
|
||||||
// lower-case, to ensure that the channel is only being tracked once.
|
|
||||||
Name string
|
Name string
|
||||||
// Topic of the channel.
|
// Topic of the channel.
|
||||||
Topic string
|
Topic string
|
||||||
// users represents the users that we can currently see within the
|
|
||||||
// channel.
|
// Users is a sorted list of all users we are currently tracking within
|
||||||
users map[string]*User
|
// the channel. Each is the nickname, and is rfc1459 compliant.
|
||||||
|
Users []string
|
||||||
// Joined represents the first time that the client joined the channel.
|
// Joined represents the first time that the client joined the channel.
|
||||||
Joined time.Time
|
Joined time.Time
|
||||||
// Modes are the known channel modes that the bot has captured.
|
// Modes are the known channel modes that the bot has captured.
|
||||||
Modes CModes
|
Modes CModes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Channel) deleteUser(nick string) {
|
||||||
|
nick = ToRFC1459(nick)
|
||||||
|
|
||||||
|
j := -1
|
||||||
|
for i := 0; i < len(c.Users); i++ {
|
||||||
|
if c.Users[i] == nick {
|
||||||
|
j = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if j != -1 {
|
||||||
|
c.Users = append(c.Users[:j], c.Users[j+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Copy returns a deep copy of a given channel.
|
// Copy returns a deep copy of a given channel.
|
||||||
func (c *Channel) Copy() *Channel {
|
func (c *Channel) Copy() *Channel {
|
||||||
nc := &Channel{}
|
nc := &Channel{}
|
||||||
*nc = *c
|
*nc = *c
|
||||||
|
|
||||||
// Copy the users.
|
_ = copy(nc.Users, c.Users)
|
||||||
nc.users = make(map[string]*User)
|
|
||||||
for k, v := range c.users {
|
|
||||||
nc.users[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// And modes.
|
// And modes.
|
||||||
nc.Modes = c.Modes.Copy()
|
nc.Modes = c.Modes.Copy()
|
||||||
@ -144,51 +204,22 @@ func (c *Channel) Copy() *Channel {
|
|||||||
return nc
|
return nc
|
||||||
}
|
}
|
||||||
|
|
||||||
// Users returns a list of users in a given channel.
|
|
||||||
func (c *Channel) Users() []*User {
|
|
||||||
out := make([]*User, len(c.users))
|
|
||||||
|
|
||||||
var index int
|
|
||||||
for _, u := range c.users {
|
|
||||||
out[index] = u
|
|
||||||
|
|
||||||
index++
|
|
||||||
}
|
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// NickList returns a list of nicknames in a given channel.
|
|
||||||
func (c *Channel) NickList() []string {
|
|
||||||
out := make([]string, len(c.users))
|
|
||||||
|
|
||||||
var index int
|
|
||||||
for k := range c.users {
|
|
||||||
out[index] = k
|
|
||||||
|
|
||||||
index++
|
|
||||||
}
|
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the count of users in a given channel.
|
// Len returns the count of users in a given channel.
|
||||||
func (c *Channel) Len() int {
|
func (c *Channel) Len() int {
|
||||||
return len(c.users)
|
return len(c.Users)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lookup looks up a user in a channel based on a given nickname. If the
|
// UserIn checks to see if a given user is in a channel.
|
||||||
// user wasn't found, user is nil.
|
func (c *Channel) UserIn(name string) bool {
|
||||||
func (c *Channel) Lookup(nick string) *User {
|
name = ToRFC1459(name)
|
||||||
for k, v := range c.users {
|
|
||||||
if ToRFC1459(k) == ToRFC1459(nick) {
|
for i := 0; i < len(c.Users); i++ {
|
||||||
// No need to have a copy, as if one has access to a channel,
|
if c.Users[i] == name {
|
||||||
// should already have a full copy.
|
return true
|
||||||
return v
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lifetime represents the amount of time that has passed since we have first
|
// Lifetime represents the amount of time that has passed since we have first
|
||||||
@ -208,43 +239,63 @@ func (s *state) createChanIfNotExists(name string) (channel *Channel) {
|
|||||||
supported := s.chanModes()
|
supported := s.chanModes()
|
||||||
prefixes, _ := parsePrefixes(s.userPrefixes())
|
prefixes, _ := parsePrefixes(s.userPrefixes())
|
||||||
|
|
||||||
name = strings.ToLower(name)
|
if _, ok := s.channels[ToRFC1459(name)]; ok {
|
||||||
if _, ok := s.channels[name]; !ok {
|
return s.channels[ToRFC1459(name)]
|
||||||
channel = &Channel{
|
|
||||||
Name: name,
|
|
||||||
users: make(map[string]*User),
|
|
||||||
Joined: time.Now(),
|
|
||||||
Modes: NewCModes(supported, prefixes),
|
|
||||||
}
|
|
||||||
s.channels[name] = channel
|
|
||||||
} else {
|
|
||||||
channel = s.channels[name]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
channel = &Channel{
|
||||||
|
Name: name,
|
||||||
|
Users: []string{},
|
||||||
|
Joined: time.Now(),
|
||||||
|
Modes: NewCModes(supported, prefixes),
|
||||||
|
}
|
||||||
|
s.channels[ToRFC1459(name)] = channel
|
||||||
|
|
||||||
return channel
|
return channel
|
||||||
}
|
}
|
||||||
|
|
||||||
// deleteChannel removes the channel from state, if not already done. Always
|
// deleteChannel removes the channel from state, if not already done. Always
|
||||||
// use state.mu for transaction.
|
// use state.mu for transaction.
|
||||||
func (s *state) deleteChannel(name string) {
|
func (s *state) deleteChannel(name string) {
|
||||||
channel := s.createChanIfNotExists(name)
|
name = ToRFC1459(name)
|
||||||
if channel == nil {
|
|
||||||
|
_, ok := s.channels[name]
|
||||||
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := s.channels[channel.Name]; ok {
|
for _, user := range s.channels[name].Users {
|
||||||
delete(s.channels, channel.Name)
|
s.users[user].deleteChannel(name)
|
||||||
|
|
||||||
|
if len(s.users[user].Channels) == 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 with a given case-insensitive
|
// lookupChannel returns a reference to a channel, nil returned if no results
|
||||||
// name. nil returned if no results found.
|
// found. Always use state.mu for transaction.
|
||||||
func (s *state) lookupChannel(name string) *Channel {
|
func (s *state) lookupChannel(name string) *Channel {
|
||||||
if !IsValidChannel(name) {
|
if !IsValidChannel(name) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.channels[strings.ToLower(name)]
|
return s.channels[ToRFC1459(name)]
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookupUser returns a reference to a user, nil returned if no results
|
||||||
|
// found. Always use state.mu for transaction.
|
||||||
|
func (s *state) lookupUser(name string) *User {
|
||||||
|
if !IsValidNick(name) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.users[ToRFC1459(name)]
|
||||||
}
|
}
|
||||||
|
|
||||||
// createUserIfNotExists creates the channel and user in state, if not already
|
// createUserIfNotExists creates the channel and user in state, if not already
|
||||||
@ -256,34 +307,66 @@ func (s *state) createUserIfNotExists(channelName, nick string) (user *User) {
|
|||||||
|
|
||||||
channel := s.createChanIfNotExists(channelName)
|
channel := s.createChanIfNotExists(channelName)
|
||||||
if channel == nil {
|
if channel == nil {
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := channel.users[nick]; ok {
|
user = s.lookupUser(nick)
|
||||||
channel.users[nick].LastActive = time.Now()
|
if user != nil {
|
||||||
return channel.users[nick]
|
if !user.InChannel(channelName) {
|
||||||
|
user.Channels = append(user.Channels, ToRFC1459(channelName))
|
||||||
|
sort.StringsAreSorted(user.Channels)
|
||||||
|
}
|
||||||
|
|
||||||
|
user.LastActive = time.Now()
|
||||||
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
user = &User{Nick: nick, FirstSeen: time.Now(), LastActive: time.Now()}
|
user = &User{
|
||||||
channel.users[nick] = user
|
Nick: nick,
|
||||||
|
FirstSeen: time.Now(),
|
||||||
|
LastActive: time.Now(),
|
||||||
|
}
|
||||||
|
s.users[ToRFC1459(nick)] = user
|
||||||
|
channel.Users = append(channel.Users, ToRFC1459(nick))
|
||||||
|
sort.Strings(channel.Users)
|
||||||
|
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
// deleteUser removes the user from channel state. Always use state.mu for
|
// deleteUser removes the user from channel state. Always use state.mu for
|
||||||
// transaction.
|
// transaction.
|
||||||
func (s *state) deleteUser(nick string) {
|
func (s *state) deleteUser(channelName, nick string) {
|
||||||
if !IsValidNick(nick) {
|
if !IsValidNick(nick) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for k := range s.channels {
|
user := s.lookupUser(nick)
|
||||||
// Check to see if they're in this channel.
|
if user == nil {
|
||||||
if _, ok := s.channels[k].users[nick]; !ok {
|
return
|
||||||
continue
|
}
|
||||||
|
|
||||||
|
if channelName == "" {
|
||||||
|
for i := 0; i < len(user.Channels); i++ {
|
||||||
|
s.channels[user.Channels[i]].deleteUser(nick)
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(s.channels[k].users, nick)
|
delete(s.users, ToRFC1459(nick))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
channel := s.lookupChannel(channelName)
|
||||||
|
if channel == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user.deleteChannel(channelName)
|
||||||
|
channel.deleteUser(nick)
|
||||||
|
|
||||||
|
if len(user.Channels) == 0 {
|
||||||
|
// This means they are no longer in any channels we track, delete
|
||||||
|
// them from state.
|
||||||
|
|
||||||
|
delete(s.users, ToRFC1459(nick))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,59 +377,29 @@ func (s *state) renameUser(from, to string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
from = ToRFC1459(from)
|
||||||
|
|
||||||
// Update our nickname.
|
// Update our nickname.
|
||||||
if from == s.nick {
|
if from == ToRFC1459(s.nick) {
|
||||||
s.nick = to
|
s.nick = to
|
||||||
}
|
}
|
||||||
|
|
||||||
for k := range s.channels {
|
user := s.lookupUser(from)
|
||||||
// Check to see if they're in this channel.
|
if user == nil {
|
||||||
if _, ok := s.channels[k].users[from]; !ok {
|
return
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Take the actual reference to the pointer.
|
|
||||||
source := *s.channels[k].users[from]
|
|
||||||
|
|
||||||
// Update the nick field (as we not only have a key, but a matching
|
|
||||||
// struct field).
|
|
||||||
source.Nick = to
|
|
||||||
source.LastActive = time.Now()
|
|
||||||
|
|
||||||
// Delete the old reference.
|
|
||||||
delete(s.channels[k].users, from)
|
|
||||||
|
|
||||||
// In with the new.
|
|
||||||
s.channels[k].users[to] = &source
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// lookupUsers returns a slice of references to users matching a given
|
delete(s.users, from)
|
||||||
// query. mathType is of "nick", "name", "ident" or "account".
|
|
||||||
func (s *state) lookupUsers(matchType, toMatch string) []*User {
|
|
||||||
var users []*User
|
|
||||||
|
|
||||||
for c := range s.channels {
|
user.Nick = to
|
||||||
for u := range s.channels[c].users {
|
user.LastActive = time.Now()
|
||||||
switch matchType {
|
s.users[ToRFC1459(to)] = user
|
||||||
case "nick":
|
|
||||||
if ToRFC1459(s.channels[c].users[u].Nick) == ToRFC1459(toMatch) {
|
for i := 0; i < len(user.Channels); i++ {
|
||||||
users = append(users, s.channels[c].users[u])
|
for j := 0; j < len(s.channels[user.Channels[i]].Users); j++ {
|
||||||
continue
|
if s.channels[user.Channels[i]].Users[j] == from {
|
||||||
}
|
s.channels[user.Channels[i]].Users[j] = ToRFC1459(to)
|
||||||
case "ident":
|
|
||||||
if ToRFC1459(s.channels[c].users[u].Ident) == ToRFC1459(toMatch) {
|
|
||||||
users = append(users, s.channels[c].users[u])
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case "account":
|
|
||||||
if ToRFC1459(s.channels[c].users[u].Extras.Account) == ToRFC1459(toMatch) {
|
|
||||||
users = append(users, s.channels[c].users[u])
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return users
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user