Collapse and log join, part and quit, closes #27, log nick and topic changes, move state into irc package
This commit is contained in:
parent
edd4d6eadb
commit
ead3b37cf9
37 changed files with 1980 additions and 969 deletions
|
@ -5,6 +5,9 @@ import (
|
|||
)
|
||||
|
||||
func (c *Client) HasCapability(name string, values ...string) bool {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if capValues, ok := c.enabledCapabilities[name]; ok {
|
||||
if len(values) == 0 || capValues == nil {
|
||||
return true
|
||||
|
|
|
@ -35,8 +35,10 @@ type Client struct {
|
|||
Messages chan *Message
|
||||
ConnectionChanged chan ConnectionState
|
||||
Features *Features
|
||||
nick string
|
||||
channels []string
|
||||
|
||||
state *state
|
||||
nick string
|
||||
channels []string
|
||||
|
||||
wantedCapabilities []string
|
||||
requestedCapabilities map[string][]string
|
||||
|
@ -80,18 +82,15 @@ func NewClient(config *Config) *Client {
|
|||
wantedCapabilities = append(wantedCapabilities, "sasl")
|
||||
}
|
||||
|
||||
return &Client{
|
||||
client := &Client{
|
||||
Config: config,
|
||||
nick: config.Nick,
|
||||
Features: NewFeatures(),
|
||||
Messages: make(chan *Message, 32),
|
||||
ConnectionChanged: make(chan ConnectionState, 4),
|
||||
Features: NewFeatures(),
|
||||
nick: config.Nick,
|
||||
wantedCapabilities: wantedCapabilities,
|
||||
requestedCapabilities: map[string][]string{},
|
||||
enabledCapabilities: map[string][]string{},
|
||||
ConnectionChanged: make(chan ConnectionState, 4),
|
||||
out: make(chan string, 32),
|
||||
quit: make(chan struct{}),
|
||||
reconnect: make(chan struct{}),
|
||||
dialer: &net.Dialer{Timeout: 10 * time.Second},
|
||||
recvBuf: make([]byte, 0, 4096),
|
||||
backoff: &backoff.Backoff{
|
||||
|
@ -99,7 +98,13 @@ func NewClient(config *Config) *Client {
|
|||
Max: 30 * time.Second,
|
||||
Jitter: true,
|
||||
},
|
||||
out: make(chan string, 32),
|
||||
quit: make(chan struct{}),
|
||||
reconnect: make(chan struct{}),
|
||||
}
|
||||
client.state = newState(client)
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
func (c *Client) GetNick() string {
|
||||
|
@ -143,6 +148,18 @@ func (c *Client) Host() string {
|
|||
return c.Config.Host
|
||||
}
|
||||
|
||||
func (c *Client) MOTD() []string {
|
||||
return c.state.getMOTD()
|
||||
}
|
||||
|
||||
func (c *Client) ChannelUsers(channel string) []string {
|
||||
return c.state.getUsers(channel)
|
||||
}
|
||||
|
||||
func (c *Client) ChannelTopic(channel string) string {
|
||||
return c.state.getTopic(channel)
|
||||
}
|
||||
|
||||
func (c *Client) Nick(nick string) {
|
||||
c.Write("NICK " + nick)
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ func (c *Client) Connect() {
|
|||
}
|
||||
|
||||
func (c *Client) Reconnect() {
|
||||
close(c.reconnect)
|
||||
c.tryConnect()
|
||||
}
|
||||
|
||||
func (c *Client) Write(data string) {
|
||||
|
@ -63,6 +63,7 @@ func (c *Client) run() {
|
|||
|
||||
c.sendRecv.Wait()
|
||||
c.reconnect = make(chan struct{})
|
||||
c.state.reset()
|
||||
|
||||
time.Sleep(c.backoff.Duration())
|
||||
c.tryConnect()
|
||||
|
@ -178,7 +179,7 @@ func (c *Client) recv() {
|
|||
|
||||
default:
|
||||
c.connChange(false, nil)
|
||||
c.Reconnect()
|
||||
close(c.reconnect)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -195,54 +196,7 @@ func (c *Client) recv() {
|
|||
return
|
||||
}
|
||||
|
||||
switch msg.Command {
|
||||
case PING:
|
||||
go c.write("PONG :" + msg.LastParam())
|
||||
|
||||
case JOIN:
|
||||
if c.Is(msg.Sender) {
|
||||
c.addChannel(msg.Params[0])
|
||||
}
|
||||
|
||||
case NICK:
|
||||
if c.Is(msg.Sender) {
|
||||
c.setNick(msg.LastParam())
|
||||
}
|
||||
|
||||
case PRIVMSG:
|
||||
if ctcp := msg.ToCTCP(); ctcp != nil {
|
||||
c.handleCTCP(ctcp, msg)
|
||||
}
|
||||
|
||||
case CAP:
|
||||
c.handleCAP(msg)
|
||||
|
||||
case RPL_WELCOME:
|
||||
c.setNick(msg.Params[0])
|
||||
c.setRegistered(true)
|
||||
c.flushChannels()
|
||||
|
||||
c.backoff.Reset()
|
||||
c.sendRecv.Add(1)
|
||||
go c.send()
|
||||
|
||||
case RPL_ISUPPORT:
|
||||
c.Features.Parse(msg.Params)
|
||||
|
||||
case ERR_NICKNAMEINUSE, ERR_NICKCOLLISION, ERR_UNAVAILRESOURCE:
|
||||
if c.Config.HandleNickInUse != nil {
|
||||
go c.writeNick(c.Config.HandleNickInUse(msg.Params[1]))
|
||||
}
|
||||
|
||||
case ERROR:
|
||||
c.Messages <- msg
|
||||
c.connChange(false, nil)
|
||||
time.Sleep(5 * time.Second)
|
||||
close(c.quit)
|
||||
return
|
||||
}
|
||||
|
||||
c.handleSASL(msg)
|
||||
c.handleMessage(msg)
|
||||
|
||||
c.Messages <- msg
|
||||
}
|
||||
|
|
|
@ -147,7 +147,7 @@ func TestRecv(t *testing.T) {
|
|||
func TestRecvTriggersReconnect(t *testing.T) {
|
||||
c := NewClient(&Config{})
|
||||
c.conn = &mockConn{}
|
||||
c.scan = bufio.NewScanner(bytes.NewBufferString("001 bob\r\n"))
|
||||
c.scan = bufio.NewScanner(bytes.NewBufferString(""))
|
||||
done := make(chan struct{})
|
||||
ok := false
|
||||
go func() {
|
||||
|
|
158
pkg/irc/internal.go
Normal file
158
pkg/irc/internal.go
Normal file
|
@ -0,0 +1,158 @@
|
|||
package irc
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (c *Client) handleMessage(msg *Message) {
|
||||
switch msg.Command {
|
||||
case CAP:
|
||||
c.handleCAP(msg)
|
||||
|
||||
case PING:
|
||||
go c.write("PONG :" + msg.LastParam())
|
||||
|
||||
case JOIN:
|
||||
if len(msg.Params) > 0 {
|
||||
channel := msg.Params[0]
|
||||
|
||||
if c.Is(msg.Sender) {
|
||||
c.addChannel(channel)
|
||||
}
|
||||
|
||||
c.state.addUser(msg.Sender, channel)
|
||||
}
|
||||
|
||||
case PART:
|
||||
if len(msg.Params) > 0 {
|
||||
channel := msg.Params[0]
|
||||
|
||||
if c.Is(msg.Sender) {
|
||||
c.state.removeChannel(channel)
|
||||
} else {
|
||||
c.state.removeUser(msg.Sender, channel)
|
||||
}
|
||||
}
|
||||
|
||||
case QUIT:
|
||||
msg.meta = c.state.removeUserAll(msg.Sender)
|
||||
|
||||
case NICK:
|
||||
if c.Is(msg.Sender) {
|
||||
c.setNick(msg.LastParam())
|
||||
}
|
||||
|
||||
msg.meta = c.state.renameUser(msg.Sender, msg.LastParam())
|
||||
|
||||
case PRIVMSG:
|
||||
if ctcp := msg.ToCTCP(); ctcp != nil {
|
||||
c.handleCTCP(ctcp, msg)
|
||||
}
|
||||
|
||||
case MODE:
|
||||
if len(msg.Params) > 1 {
|
||||
target := msg.Params[0]
|
||||
if len(msg.Params) > 2 && isChannel(target) {
|
||||
mode := ParseMode(msg.Params[1])
|
||||
mode.Server = c.Host()
|
||||
mode.Channel = target
|
||||
mode.User = msg.Params[2]
|
||||
|
||||
c.state.setMode(target, msg.Params[2], mode.Add, mode.Remove)
|
||||
|
||||
msg.meta = mode
|
||||
}
|
||||
}
|
||||
|
||||
case TOPIC, RPL_TOPIC:
|
||||
chIndex := 0
|
||||
if msg.Command == RPL_TOPIC {
|
||||
chIndex = 1
|
||||
}
|
||||
|
||||
if len(msg.Params) > chIndex {
|
||||
c.state.setTopic(msg.LastParam(), msg.Params[chIndex])
|
||||
}
|
||||
|
||||
case RPL_NOTOPIC:
|
||||
if len(msg.Params) > 1 {
|
||||
channel := msg.Params[1]
|
||||
c.state.setTopic("", channel)
|
||||
}
|
||||
|
||||
case RPL_WELCOME:
|
||||
if len(msg.Params) > 0 {
|
||||
c.setNick(msg.Params[0])
|
||||
}
|
||||
c.setRegistered(true)
|
||||
c.flushChannels()
|
||||
|
||||
c.backoff.Reset()
|
||||
c.sendRecv.Add(1)
|
||||
go c.send()
|
||||
|
||||
case RPL_ISUPPORT:
|
||||
c.Features.Parse(msg.Params)
|
||||
|
||||
case ERR_NICKNAMEINUSE, ERR_NICKCOLLISION, ERR_UNAVAILRESOURCE:
|
||||
if c.Config.HandleNickInUse != nil && len(msg.Params) > 1 {
|
||||
go c.writeNick(c.Config.HandleNickInUse(msg.Params[1]))
|
||||
}
|
||||
|
||||
case RPL_NAMREPLY:
|
||||
channel := msg.Params[2]
|
||||
users := strings.Split(strings.TrimSuffix(msg.LastParam(), " "), " ")
|
||||
|
||||
userBuffer := c.state.userBuffers[channel]
|
||||
c.state.userBuffers[channel] = append(userBuffer, users...)
|
||||
|
||||
case RPL_ENDOFNAMES:
|
||||
channel := msg.Params[1]
|
||||
users := c.state.userBuffers[channel]
|
||||
|
||||
c.state.setUsers(users, channel)
|
||||
delete(c.state.userBuffers, channel)
|
||||
msg.meta = users
|
||||
|
||||
case ERROR:
|
||||
c.Messages <- msg
|
||||
c.connChange(false, nil)
|
||||
time.Sleep(5 * time.Second)
|
||||
close(c.quit)
|
||||
return
|
||||
}
|
||||
|
||||
c.handleSASL(msg)
|
||||
}
|
||||
|
||||
type Mode struct {
|
||||
Server string
|
||||
Channel string
|
||||
User string
|
||||
Add string
|
||||
Remove string
|
||||
}
|
||||
|
||||
func ParseMode(mode string) *Mode {
|
||||
m := Mode{}
|
||||
add := false
|
||||
|
||||
for _, c := range mode {
|
||||
if c == '+' {
|
||||
add = true
|
||||
} else if c == '-' {
|
||||
add = false
|
||||
} else if add {
|
||||
m.Add += string(c)
|
||||
} else {
|
||||
m.Remove += string(c)
|
||||
}
|
||||
}
|
||||
|
||||
return &m
|
||||
}
|
||||
|
||||
func isChannel(s string) bool {
|
||||
return strings.IndexAny(s, "&#+!") == 0
|
||||
}
|
37
pkg/irc/internal_test.go
Normal file
37
pkg/irc/internal_test.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
package irc
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHandlePing(t *testing.T) {
|
||||
c, out := testClientSend()
|
||||
c.handleMessage(&Message{
|
||||
Command: "PING",
|
||||
Params: []string{"voi voi"},
|
||||
})
|
||||
assert.Equal(t, "PONG :voi voi\r\n", <-out)
|
||||
}
|
||||
|
||||
func TestHandleNamreply(t *testing.T) {
|
||||
c, _ := testClientSend()
|
||||
|
||||
c.handleMessage(&Message{
|
||||
Command: RPL_NAMREPLY,
|
||||
Params: []string{"", "", "#chan", "a b c"},
|
||||
})
|
||||
c.handleMessage(&Message{
|
||||
Command: RPL_NAMREPLY,
|
||||
Params: []string{"", "", "#chan", "d"},
|
||||
})
|
||||
|
||||
endMsg := &Message{
|
||||
Command: RPL_ENDOFNAMES,
|
||||
Params: []string{"", "#chan"},
|
||||
}
|
||||
c.handleMessage(endMsg)
|
||||
|
||||
assert.Equal(t, []string{"a", "b", "c", "d"}, endMsg.meta)
|
||||
}
|
|
@ -11,6 +11,8 @@ type Message struct {
|
|||
Host string
|
||||
Command string
|
||||
Params []string
|
||||
|
||||
meta interface{}
|
||||
}
|
||||
|
||||
func (m *Message) LastParam() string {
|
||||
|
|
33
pkg/irc/meta.go
Normal file
33
pkg/irc/meta.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
package irc
|
||||
|
||||
// GetNickChannels returns the channels the client has in common with
|
||||
// the user that changed nick
|
||||
func GetNickChannels(msg *Message) []string {
|
||||
return stringListMeta(msg)
|
||||
}
|
||||
|
||||
// GetQuitChannels returns the channels the client has in common with
|
||||
// the user that quit
|
||||
func GetQuitChannels(msg *Message) []string {
|
||||
return stringListMeta(msg)
|
||||
}
|
||||
|
||||
func GetMode(msg *Message) *Mode {
|
||||
if mode, ok := msg.meta.(*Mode); ok {
|
||||
return mode
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetNamreplyUsers returns all RPL_NAMREPLY users
|
||||
// when passed a RPL_ENDOFNAMES message
|
||||
func GetNamreplyUsers(msg *Message) []string {
|
||||
return stringListMeta(msg)
|
||||
}
|
||||
|
||||
func stringListMeta(msg *Message) []string {
|
||||
if list, ok := msg.meta.([]string); ok {
|
||||
return list
|
||||
}
|
||||
return nil
|
||||
}
|
236
pkg/irc/state.go
Normal file
236
pkg/irc/state.go
Normal file
|
@ -0,0 +1,236 @@
|
|||
package irc
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type state struct {
|
||||
client *Client
|
||||
|
||||
users map[string][]*User
|
||||
topic map[string]string
|
||||
|
||||
userBuffers map[string][]string
|
||||
|
||||
motd []string
|
||||
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
const userModePrefixes = "~&@%+"
|
||||
const userModeChars = "qaohv"
|
||||
|
||||
type User struct {
|
||||
nick string
|
||||
modes string
|
||||
prefix string
|
||||
}
|
||||
|
||||
func NewUser(nick string) *User {
|
||||
user := &User{nick: nick}
|
||||
|
||||
if i := strings.IndexAny(nick, userModePrefixes); i == 0 {
|
||||
i = strings.Index(userModePrefixes, string(nick[0]))
|
||||
user.modes = string(userModeChars[i])
|
||||
user.nick = nick[1:]
|
||||
user.updatePrefix()
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
func (u *User) String() string {
|
||||
return u.prefix + u.nick
|
||||
}
|
||||
|
||||
func (u *User) AddModes(modes string) {
|
||||
for _, mode := range modes {
|
||||
if strings.Contains(u.modes, string(mode)) {
|
||||
continue
|
||||
}
|
||||
u.modes += string(mode)
|
||||
}
|
||||
u.updatePrefix()
|
||||
}
|
||||
|
||||
func (u *User) RemoveModes(modes string) {
|
||||
for _, mode := range modes {
|
||||
u.modes = strings.Replace(u.modes, string(mode), "", 1)
|
||||
}
|
||||
u.updatePrefix()
|
||||
}
|
||||
|
||||
func (u *User) updatePrefix() {
|
||||
for i, mode := range userModeChars {
|
||||
if strings.Contains(u.modes, string(mode)) {
|
||||
u.prefix = string(userModePrefixes[i])
|
||||
return
|
||||
}
|
||||
}
|
||||
u.prefix = ""
|
||||
}
|
||||
|
||||
func newState(client *Client) *state {
|
||||
return &state{
|
||||
client: client,
|
||||
users: make(map[string][]*User),
|
||||
topic: make(map[string]string),
|
||||
userBuffers: make(map[string][]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) reset() {
|
||||
s.lock.Lock()
|
||||
s.users = make(map[string][]*User)
|
||||
s.topic = make(map[string]string)
|
||||
s.userBuffers = make(map[string][]string)
|
||||
s.motd = []string{}
|
||||
s.lock.Unlock()
|
||||
}
|
||||
|
||||
func (s *state) removeChannel(channel string) {
|
||||
s.lock.Lock()
|
||||
delete(s.users, channel)
|
||||
delete(s.topic, channel)
|
||||
s.lock.Unlock()
|
||||
}
|
||||
|
||||
func (s *state) getUsers(channel string) []string {
|
||||
s.lock.Lock()
|
||||
|
||||
users := make([]string, len(s.users[channel]))
|
||||
for i, user := range s.users[channel] {
|
||||
users[i] = user.String()
|
||||
}
|
||||
|
||||
s.lock.Unlock()
|
||||
|
||||
return users
|
||||
}
|
||||
|
||||
func (s *state) setUsers(users []string, channel string) {
|
||||
s.lock.Lock()
|
||||
|
||||
s.users[channel] = make([]*User, len(users))
|
||||
for i, nick := range users {
|
||||
s.users[channel][i] = NewUser(nick)
|
||||
}
|
||||
|
||||
s.lock.Unlock()
|
||||
}
|
||||
|
||||
func (s *state) addUser(user, channel string) {
|
||||
s.lock.Lock()
|
||||
|
||||
if users, ok := s.users[channel]; ok {
|
||||
for _, u := range users {
|
||||
if u.nick == user {
|
||||
s.lock.Unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
s.users[channel] = append(users, NewUser(user))
|
||||
} else {
|
||||
s.users[channel] = []*User{NewUser(user)}
|
||||
}
|
||||
|
||||
s.lock.Unlock()
|
||||
}
|
||||
|
||||
func (s *state) removeUser(user, channel string) {
|
||||
s.lock.Lock()
|
||||
s.internalRemoveUser(user, channel)
|
||||
s.lock.Unlock()
|
||||
}
|
||||
|
||||
func (s *state) removeUserAll(user string) []string {
|
||||
channels := []string{}
|
||||
s.lock.Lock()
|
||||
|
||||
for channel := range s.users {
|
||||
if s.internalRemoveUser(user, channel) {
|
||||
channels = append(channels, channel)
|
||||
}
|
||||
}
|
||||
|
||||
s.lock.Unlock()
|
||||
return channels
|
||||
}
|
||||
|
||||
func (s *state) renameUser(oldNick, newNick string) []string {
|
||||
s.lock.Lock()
|
||||
channels := s.renameAll(oldNick, newNick)
|
||||
s.lock.Unlock()
|
||||
return channels
|
||||
}
|
||||
|
||||
func (s *state) setMode(channel, user, add, remove string) {
|
||||
s.lock.Lock()
|
||||
|
||||
for _, u := range s.users[channel] {
|
||||
if u.nick == user {
|
||||
u.AddModes(add)
|
||||
u.RemoveModes(remove)
|
||||
|
||||
s.lock.Unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
s.lock.Unlock()
|
||||
}
|
||||
|
||||
func (s *state) getTopic(channel string) string {
|
||||
s.lock.Lock()
|
||||
topic := s.topic[channel]
|
||||
s.lock.Unlock()
|
||||
return topic
|
||||
}
|
||||
|
||||
func (s *state) setTopic(topic, channel string) {
|
||||
s.lock.Lock()
|
||||
s.topic[channel] = topic
|
||||
s.lock.Unlock()
|
||||
}
|
||||
|
||||
func (s *state) getMOTD() []string {
|
||||
s.lock.Lock()
|
||||
motd := s.motd
|
||||
s.lock.Unlock()
|
||||
return motd
|
||||
}
|
||||
|
||||
func (s *state) rename(channel, oldNick, newNick string) bool {
|
||||
for _, user := range s.users[channel] {
|
||||
if user.nick == oldNick {
|
||||
user.nick = newNick
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *state) renameAll(oldNick, newNick string) []string {
|
||||
channels := []string{}
|
||||
|
||||
for channel := range s.users {
|
||||
if s.rename(channel, oldNick, newNick) {
|
||||
channels = append(channels, channel)
|
||||
}
|
||||
}
|
||||
|
||||
return channels
|
||||
}
|
||||
|
||||
func (s *state) internalRemoveUser(user, channel string) bool {
|
||||
for i, u := range s.users[channel] {
|
||||
if u.nick == user {
|
||||
users := s.users[channel]
|
||||
s.users[channel] = append(users[:i], users[i+1:]...)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
89
pkg/irc/state_test.go
Normal file
89
pkg/irc/state_test.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
package irc
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStateGetSetUsers(t *testing.T) {
|
||||
state := newState(NewClient(&Config{}))
|
||||
users := []string{"a", "b"}
|
||||
state.setUsers(users, "#chan")
|
||||
assert.Equal(t, users, state.getUsers("#chan"))
|
||||
state.setUsers(users, "#chan")
|
||||
assert.Equal(t, users, state.getUsers("#chan"))
|
||||
}
|
||||
|
||||
func TestStateAddRemoveUser(t *testing.T) {
|
||||
state := newState(NewClient(&Config{}))
|
||||
state.addUser("user", "#chan")
|
||||
state.addUser("user", "#chan")
|
||||
assert.Len(t, state.getUsers("#chan"), 1)
|
||||
state.addUser("user2", "#chan")
|
||||
assert.Equal(t, []string{"user", "user2"}, state.getUsers("#chan"))
|
||||
state.removeUser("user", "#chan")
|
||||
assert.Equal(t, []string{"user2"}, state.getUsers("#chan"))
|
||||
}
|
||||
|
||||
func TestStateRemoveUserAll(t *testing.T) {
|
||||
state := newState(NewClient(&Config{}))
|
||||
state.addUser("user", "#chan1")
|
||||
state.addUser("user", "#chan2")
|
||||
state.removeUserAll("user")
|
||||
assert.Empty(t, state.getUsers("#chan1"))
|
||||
assert.Empty(t, state.getUsers("#chan2"))
|
||||
}
|
||||
|
||||
func TestStateRenameUser(t *testing.T) {
|
||||
state := newState(NewClient(&Config{}))
|
||||
state.addUser("user", "#chan1")
|
||||
state.addUser("user", "#chan2")
|
||||
state.renameUser("user", "new")
|
||||
assert.Equal(t, []string{"new"}, state.getUsers("#chan1"))
|
||||
assert.Equal(t, []string{"new"}, state.getUsers("#chan2"))
|
||||
|
||||
state.addUser("@gotop", "#chan3")
|
||||
state.renameUser("gotop", "stillgotit")
|
||||
assert.Equal(t, []string{"@stillgotit"}, state.getUsers("#chan3"))
|
||||
}
|
||||
|
||||
func TestStateMode(t *testing.T) {
|
||||
state := newState(NewClient(&Config{}))
|
||||
state.addUser("+user", "#chan")
|
||||
state.setMode("#chan", "user", "o", "v")
|
||||
assert.Equal(t, []string{"@user"}, state.getUsers("#chan"))
|
||||
state.setMode("#chan", "user", "v", "")
|
||||
assert.Equal(t, []string{"@user"}, state.getUsers("#chan"))
|
||||
state.setMode("#chan", "user", "", "o")
|
||||
assert.Equal(t, []string{"+user"}, state.getUsers("#chan"))
|
||||
state.setMode("#chan", "user", "q", "")
|
||||
assert.Equal(t, []string{"~user"}, state.getUsers("#chan"))
|
||||
}
|
||||
|
||||
func TestStateTopic(t *testing.T) {
|
||||
state := newState(NewClient(&Config{}))
|
||||
assert.Equal(t, "", state.getTopic("#chan"))
|
||||
state.setTopic("the topic", "#chan")
|
||||
assert.Equal(t, "the topic", state.getTopic("#chan"))
|
||||
}
|
||||
|
||||
func TestStateChannelUserMode(t *testing.T) {
|
||||
user := NewUser("&test")
|
||||
assert.Equal(t, "test", user.nick)
|
||||
assert.Equal(t, "a", string(user.modes[0]))
|
||||
assert.Equal(t, "&test", user.String())
|
||||
|
||||
user.RemoveModes("a")
|
||||
assert.Equal(t, "test", user.String())
|
||||
user.AddModes("o")
|
||||
assert.Equal(t, "@test", user.String())
|
||||
user.AddModes("q")
|
||||
assert.Equal(t, "~test", user.String())
|
||||
user.AddModes("v")
|
||||
assert.Equal(t, "~test", user.String())
|
||||
user.RemoveModes("qo")
|
||||
assert.Equal(t, "+test", user.String())
|
||||
user.RemoveModes("v")
|
||||
assert.Equal(t, "test", user.String())
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue