1
0
Fork 0
mirror of https://github.com/fluffle/goirc synced 2025-05-12 18:44:50 +00:00

Merge branch 'state-copy'. Fixes #49, #35.

Overhaul the state tracker to return copies of data that should be correct
at the time of the response. Subsequent changes to tracked IRC state will not
be reflected in the copies. For people fixing up their code because this merge
broke everything, you probably want to be paying particular attention to the
use of conn.Me() instead of conn.cfg.Me.

Sorry if this causes you hassle, it's for the best.

Lastly, if kballard is watching: sorry dude, you were mostly right ;-)
This commit is contained in:
Alex Bramley 2015-02-27 19:56:41 +00:00
commit 0cac69d2ee
12 changed files with 958 additions and 576 deletions

View file

@ -90,7 +90,7 @@ type Config struct {
func NewConfig(nick string, args ...string) *Config {
cfg := &Config{
Me: state.NewNick(nick),
Me: &state.Nick{Nick: nick},
PingFreq: 3 * time.Minute,
NewNick: func(s string) string { return s + "_" },
Recover: (*Conn).LogPanic, // in dispatch.go
@ -122,7 +122,7 @@ func Client(cfg *Config) *Conn {
cfg = NewConfig("__idiot__")
}
if cfg.Me == nil || cfg.Me.Nick == "" || cfg.Me.Ident == "" {
cfg.Me = state.NewNick("__idiot__")
cfg.Me = &state.Nick{Nick: "__idiot__"}
cfg.Me.Ident = "goirc"
cfg.Me.Name = "Powered by GoIRC"
}
@ -169,6 +169,11 @@ func (conn *Conn) Config() *Config {
}
func (conn *Conn) Me() *state.Nick {
conn.mu.RLock()
defer conn.mu.RUnlock()
if conn.st != nil {
conn.cfg.Me = conn.st.Me()
}
return conn.cfg.Me
}
@ -177,6 +182,8 @@ func (conn *Conn) StateTracker() state.Tracker {
}
func (conn *Conn) EnableStateTracking() {
conn.mu.Lock()
defer conn.mu.Unlock()
if conn.st == nil {
n := conn.cfg.Me
conn.st = state.NewTracker(n.Nick)
@ -188,7 +195,10 @@ func (conn *Conn) EnableStateTracking() {
}
func (conn *Conn) DisableStateTracking() {
conn.mu.Lock()
defer conn.mu.Unlock()
if conn.st != nil {
conn.cfg.Me = conn.st.Me()
conn.delSTHandlers()
conn.st.Wipe()
conn.st = nil

View file

@ -128,20 +128,23 @@ func TestClientAndStateTracking(t *testing.T) {
}
// We're expecting the untracked me to be replaced by a tracked one
if c.st == nil {
t.Errorf("State tracker not enabled correctly.")
}
if me := c.cfg.Me; me.Nick != "test" || me.Ident != "test" ||
me.Name != "Testing IRC" || me.Host != "" {
t.Errorf("Enabling state tracking did not replace Me correctly.")
}
if c.st == nil || c.cfg.Me != c.st.Me() {
t.Errorf("State tracker not enabled correctly.")
}
// Now, shim in the mock state tracker and test disabling state tracking
me := c.cfg.Me
c.st = st
st.EXPECT().Wipe()
gomock.InOrder(
st.EXPECT().Me().Return(me),
st.EXPECT().Wipe(),
)
c.DisableStateTracking()
if c.st != nil || c.cfg.Me != me {
if c.st != nil || !c.cfg.Me.Equals(me) {
t.Errorf("State tracker not disabled correctly.")
}

View file

@ -49,7 +49,12 @@ func (conn *Conn) h_001(line *Line) {
if idx := strings.LastIndex(t, " "); idx != -1 {
t = t[idx+1:]
if idx = strings.Index(t, "@"); idx != -1 {
conn.cfg.Me.Host = t[idx+1:]
if conn.st != nil {
me := conn.Me()
conn.st.NickInfo(me.Nick, me.Ident, t[idx+1:], me.Name)
} else {
conn.cfg.Me.Host = t[idx+1:]
}
}
}
}
@ -65,14 +70,15 @@ func (conn *Conn) h_001(line *Line) {
// Handler to deal with "433 :Nickname already in use"
func (conn *Conn) h_433(line *Line) {
// Args[1] is the new nick we were attempting to acquire
me := conn.Me()
neu := conn.cfg.NewNick(line.Args[1])
conn.Nick(neu)
// if this is happening before we're properly connected (i.e. the nick
// we sent in the initial NICK command is in use) we will not receive
// a NICK message to confirm our change of nick, so ReNick here...
if line.Args[1] == conn.cfg.Me.Nick {
if line.Args[1] == me.Nick {
if conn.st != nil {
conn.st.ReNick(conn.cfg.Me.Nick, neu)
conn.cfg.Me = conn.st.ReNick(me.Nick, neu)
} else {
conn.cfg.Me.Nick = neu
}

View file

@ -50,6 +50,11 @@ func Test001(t *testing.T) {
hcon = true
})
// Test state tracking first.
gomock.InOrder(
s.st.EXPECT().Me().Return(c.cfg.Me),
s.st.EXPECT().NickInfo("test", "test", "somehost.com", "Testing IRC"),
)
// Call handler with a valid 001 line
c.h_001(l)
<-time.After(time.Millisecond)
@ -57,10 +62,14 @@ func Test001(t *testing.T) {
t.Errorf("001 handler did not dispatch connected event.")
}
// Now without state tracking.
c.st = nil
c.h_001(l)
// Check host parsed correctly
if c.cfg.Me.Host != "somehost.com" {
t.Errorf("Host parsing failed, host is '%s'.", c.cfg.Me.Host)
}
c.st = s.st
}
// Test the handler for 433 / ERR_NICKNAMEINUSE
@ -69,29 +78,21 @@ func Test433(t *testing.T) {
defer s.tearDown()
// Call handler with a 433 line, not triggering c.cfg.Me.Renick()
s.st.EXPECT().Me().Return(c.cfg.Me)
c.h_433(ParseLine(":irc.server.org 433 test new :Nickname is already in use."))
s.nc.Expect("NICK new_")
// In this case, we're expecting the server to send a NICK line
if c.cfg.Me.Nick != "test" {
t.Errorf("ReNick() called unexpectedly, Nick == '%s'.", c.cfg.Me.Nick)
}
// Send a line that will trigger a renick. This happens when our wanted
// nick is unavailable during initial negotiation, so we must choose a
// different one before the connection can proceed. No NICK line will be
// sent by the server to confirm nick change in this case.
s.st.EXPECT().ReNick("test", "test_")
gomock.InOrder(
s.st.EXPECT().Me().Return(c.cfg.Me),
s.st.EXPECT().ReNick("test", "test_").Return(c.cfg.Me),
)
c.h_433(ParseLine(":irc.server.org 433 test test :Nickname is already in use."))
s.nc.Expect("NICK test_")
// Counter-intuitively, c.cfg.Me.Nick will not change in this case. This
// is an artifact of the test set-up, with a mocked out state tracker that
// doesn't actually change any state. Normally, this would be fine :-)
if c.cfg.Me.Nick != "test" {
t.Errorf("My nick changed from '%s'.", c.cfg.Me.Nick)
}
// Test the code path that *doesn't* involve state tracking.
c.st = nil
c.h_433(ParseLine(":irc.server.org 433 test test :Nickname is already in use."))
@ -164,13 +165,14 @@ func TestJOIN(t *testing.T) {
defer s.tearDown()
// The state tracker should be creating a new channel in this first test
chan1 := state.NewChannel("#test1")
chan1 := &state.Channel{Name: "#test1"}
gomock.InOrder(
s.st.EXPECT().GetChannel("#test1").Return(nil),
s.st.EXPECT().GetNick("test").Return(c.cfg.Me),
s.st.EXPECT().Me().Return(c.cfg.Me),
s.st.EXPECT().NewChannel("#test1").Return(chan1),
s.st.EXPECT().Associate(chan1, c.cfg.Me),
s.st.EXPECT().Associate("#test1", "test"),
)
// Use #test1 to test expected behaviour
@ -182,13 +184,14 @@ func TestJOIN(t *testing.T) {
s.nc.Expect("WHO #test1")
// In this second test, we should be creating a new nick
nick1 := state.NewNick("user1")
nick1 := &state.Nick{Nick: "user1"}
gomock.InOrder(
s.st.EXPECT().GetChannel("#test1").Return(chan1),
s.st.EXPECT().GetNick("user1").Return(nil),
s.st.EXPECT().NewNick("user1").Return(nick1),
s.st.EXPECT().Associate(chan1, nick1),
s.st.EXPECT().NickInfo("user1", "ident1", "host1.com", "").Return(nick1),
s.st.EXPECT().Associate("#test1", "user1"),
)
// OK, now #test1 exists, JOIN another user we don't know about
@ -198,11 +201,11 @@ func TestJOIN(t *testing.T) {
s.nc.Expect("WHO user1")
// In this third test, we'll be pretending we know about the nick already.
nick2 := state.NewNick("user2")
nick2 := &state.Nick{Nick: "user2"}
gomock.InOrder(
s.st.EXPECT().GetChannel("#test1").Return(chan1),
s.st.EXPECT().GetNick("user2").Return(nick2),
s.st.EXPECT().Associate(chan1, nick2),
s.st.EXPECT().Associate("#test1", "user2"),
)
c.h_JOIN(ParseLine(":user2!ident2@host2.com JOIN :#test1"))
@ -211,9 +214,11 @@ func TestJOIN(t *testing.T) {
// unknown channel, unknown nick
s.st.EXPECT().GetChannel("#test2").Return(nil),
s.st.EXPECT().GetNick("blah").Return(nil),
s.st.EXPECT().Me().Return(c.cfg.Me),
// unknown channel, known nick that isn't Me.
s.st.EXPECT().GetChannel("#test2").Return(nil),
s.st.EXPECT().GetNick("user2").Return(nick2),
s.st.EXPECT().Me().Return(c.cfg.Me),
)
c.h_JOIN(ParseLine(":blah!moo@cows.com JOIN :#test2"))
c.h_JOIN(ParseLine(":user2!ident2@host2.com JOIN :#test2"))
@ -224,16 +229,8 @@ func TestPART(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
// We need some valid and associated nicks / channels to PART with.
chan1 := state.NewChannel("#test1")
nick1 := state.NewNick("user1")
// PART should dissociate a nick from a channel.
gomock.InOrder(
s.st.EXPECT().GetChannel("#test1").Return(chan1),
s.st.EXPECT().GetNick("user1").Return(nick1),
s.st.EXPECT().Dissociate(chan1, nick1),
)
s.st.EXPECT().Dissociate("#test1", "user1")
c.h_PART(ParseLine(":user1!ident1@host1.com PART #test1 :Bye!"))
}
@ -243,16 +240,8 @@ func TestKICK(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
// We need some valid and associated nicks / channels to KICK.
chan1 := state.NewChannel("#test1")
nick1 := state.NewNick("user1")
// KICK should dissociate a nick from a channel.
gomock.InOrder(
s.st.EXPECT().GetChannel("#test1").Return(chan1),
s.st.EXPECT().GetNick("user1").Return(nick1),
s.st.EXPECT().Dissociate(chan1, nick1),
)
s.st.EXPECT().Dissociate("#test1", "user1")
c.h_KICK(ParseLine(":test!test@somehost.com KICK #test1 user1 :Bye!"))
}
@ -271,34 +260,28 @@ func TestMODE(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
chan1 := state.NewChannel("#test1")
nick1 := state.NewNick("user1")
// Send a channel mode line. Inconveniently, Channel and Nick objects
// aren't mockable with gomock as they're not interface types (and I
// don't want them to be, writing accessors for struct fields sucks).
// This makes testing whether ParseModes is called correctly harder.
s.st.EXPECT().GetChannel("#test1").Return(chan1)
// Channel modes
gomock.InOrder(
s.st.EXPECT().GetChannel("#test1").Return(&state.Channel{Name: "#test1"}),
s.st.EXPECT().ChannelModes("#test1", "+sk", "somekey"),
)
c.h_MODE(ParseLine(":user1!ident1@host1.com MODE #test1 +sk somekey"))
if !chan1.Modes.Secret || chan1.Modes.Key != "somekey" {
t.Errorf("Channel.ParseModes() not called correctly.")
}
// Send a nick mode line, returning Me
// Nick modes for Me.
gomock.InOrder(
s.st.EXPECT().GetChannel("test").Return(nil),
s.st.EXPECT().GetNick("test").Return(c.cfg.Me),
s.st.EXPECT().Me().Return(c.cfg.Me),
s.st.EXPECT().NickModes("test", "+i"),
)
c.h_MODE(ParseLine(":test!test@somehost.com MODE test +i"))
if !c.cfg.Me.Modes.Invisible {
t.Errorf("Nick.ParseModes() not called correctly.")
}
// Check error paths
gomock.InOrder(
// send a valid user mode that's not us
s.st.EXPECT().GetChannel("user1").Return(nil),
s.st.EXPECT().GetNick("user1").Return(nick1),
s.st.EXPECT().GetNick("user1").Return(&state.Nick{Nick: "user1"}),
s.st.EXPECT().Me().Return(c.cfg.Me),
// Send a random mode for an unknown channel
s.st.EXPECT().GetChannel("#test2").Return(nil),
s.st.EXPECT().GetNick("#test2").Return(nil),
@ -312,22 +295,13 @@ func TestTOPIC(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
chan1 := state.NewChannel("#test1")
// Assert that it has no topic originally
if chan1.Topic != "" {
t.Errorf("Test channel already has a topic.")
}
// Send a TOPIC line
s.st.EXPECT().GetChannel("#test1").Return(chan1)
// Ensure TOPIC reply calls Topic
gomock.InOrder(
s.st.EXPECT().GetChannel("#test1").Return(&state.Channel{Name: "#test1"}),
s.st.EXPECT().Topic("#test1", "something something"),
)
c.h_TOPIC(ParseLine(":user1!ident1@host1.com TOPIC #test1 :something something"))
// Make sure the channel's topic has been changed
if chan1.Topic != "something something" {
t.Errorf("Topic of test channel not set correctly.")
}
// Check error paths -- send a topic for an unknown channel
s.st.EXPECT().GetChannel("#test2").Return(nil)
c.h_TOPIC(ParseLine(":user1!ident1@host1.com TOPIC #test2 :dark side"))
@ -338,20 +312,14 @@ func Test311(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
// Create user1, who we know little about
nick1 := state.NewNick("user1")
// Send a 311 reply
s.st.EXPECT().GetNick("user1").Return(nick1)
// Ensure 311 reply calls NickInfo
gomock.InOrder(
s.st.EXPECT().GetNick("user1").Return(&state.Nick{Nick: "user1"}),
s.st.EXPECT().Me().Return(c.cfg.Me),
s.st.EXPECT().NickInfo("user1", "ident1", "host1.com", "name"),
)
c.h_311(ParseLine(":irc.server.org 311 test user1 ident1 host1.com * :name"))
// Verify we now know more about user1
if nick1.Ident != "ident1" ||
nick1.Host != "host1.com" ||
nick1.Name != "name" {
t.Errorf("WHOIS info of user1 not set correctly.")
}
// Check error paths -- send a 311 for an unknown nick
s.st.EXPECT().GetNick("user2").Return(nil)
c.h_311(ParseLine(":irc.server.org 311 test user2 ident2 host2.com * :dongs"))
@ -362,15 +330,12 @@ func Test324(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
// Create #test1, whose modes we don't know
chan1 := state.NewChannel("#test1")
// Send a 324 reply
s.st.EXPECT().GetChannel("#test1").Return(chan1)
// Ensure 324 reply calls ChannelModes
gomock.InOrder(
s.st.EXPECT().GetChannel("#test1").Return(&state.Channel{Name: "#test1"}),
s.st.EXPECT().ChannelModes("#test1", "+sk", "somekey"),
)
c.h_324(ParseLine(":irc.server.org 324 test #test1 +sk somekey"))
if !chan1.Modes.Secret || chan1.Modes.Key != "somekey" {
t.Errorf("Channel.ParseModes() not called correctly.")
}
// Check error paths -- send 324 for an unknown channel
s.st.EXPECT().GetChannel("#test2").Return(nil)
@ -382,23 +347,13 @@ func Test332(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
// Create #test1, whose topic we don't know
chan1 := state.NewChannel("#test1")
// Assert that it has no topic originally
if chan1.Topic != "" {
t.Errorf("Test channel already has a topic.")
}
// Send a 332 reply
s.st.EXPECT().GetChannel("#test1").Return(chan1)
// Ensure 332 reply calls Topic
gomock.InOrder(
s.st.EXPECT().GetChannel("#test1").Return(&state.Channel{Name: "#test1"}),
s.st.EXPECT().Topic("#test1", "something something"),
)
c.h_332(ParseLine(":irc.server.org 332 test #test1 :something something"))
// Make sure the channel's topic has been changed
if chan1.Topic != "something something" {
t.Errorf("Topic of test channel not set correctly.")
}
// Check error paths -- send 332 for an unknown channel
s.st.EXPECT().GetChannel("#test2").Return(nil)
c.h_332(ParseLine(":irc.server.org 332 test #test2 :dark side"))
@ -409,30 +364,24 @@ func Test352(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
// Create user1, who we know little about
nick1 := state.NewNick("user1")
// Send a 352 reply
s.st.EXPECT().GetNick("user1").Return(nick1)
// Ensure 352 reply calls NickInfo and NickModes
gomock.InOrder(
s.st.EXPECT().GetNick("user1").Return(&state.Nick{Nick: "user1"}),
s.st.EXPECT().Me().Return(c.cfg.Me),
s.st.EXPECT().NickInfo("user1", "ident1", "host1.com", "name"),
)
c.h_352(ParseLine(":irc.server.org 352 test #test1 ident1 host1.com irc.server.org user1 G :0 name"))
// Verify we now know more about user1
if nick1.Ident != "ident1" ||
nick1.Host != "host1.com" ||
nick1.Name != "name" ||
nick1.Modes.Invisible ||
nick1.Modes.Oper {
t.Errorf("WHO info of user1 not set correctly.")
}
// Check that modes are set correctly from WHOREPLY
s.st.EXPECT().GetNick("user1").Return(nick1)
gomock.InOrder(
s.st.EXPECT().GetNick("user1").Return(&state.Nick{Nick: "user1"}),
s.st.EXPECT().Me().Return(c.cfg.Me),
s.st.EXPECT().NickInfo("user1", "ident1", "host1.com", "name"),
s.st.EXPECT().NickModes("user1", "+o"),
s.st.EXPECT().NickModes("user1", "+i"),
)
c.h_352(ParseLine(":irc.server.org 352 test #test1 ident1 host1.com irc.server.org user1 H* :0 name"))
if !nick1.Modes.Invisible || !nick1.Modes.Oper {
t.Errorf("WHO modes of user1 not set correctly.")
}
// Check error paths -- send a 352 for an unknown nick
s.st.EXPECT().GetNick("user2").Return(nil)
c.h_352(ParseLine(":irc.server.org 352 test #test2 ident2 host2.com irc.server.org user2 G :0 fooo"))
@ -443,55 +392,42 @@ func Test353(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
// Create #test1, whose user list we're mostly unfamiliar with
chan1 := state.NewChannel("#test1")
// Create maps for testing -- this is what the mock ST calls will return
nicks := make(map[string]*state.Nick)
privs := make(map[string]*state.ChanPrivs)
nicks["test"] = c.cfg.Me
privs["test"] = new(state.ChanPrivs)
for _, n := range []string{"user1", "user2", "voice", "halfop",
"op", "admin", "owner"} {
nicks[n] = state.NewNick(n)
privs[n] = new(state.ChanPrivs)
}
// 353 handler is called twice, so GetChannel will be called twice
s.st.EXPECT().GetChannel("#test1").Return(chan1).Times(2)
s.st.EXPECT().GetChannel("#test1").Return(&state.Channel{Name: "#test1"}).Times(2)
gomock.InOrder(
// "test" is Me, i am known, and already on the channel
s.st.EXPECT().GetNick("test").Return(c.cfg.Me),
s.st.EXPECT().IsOn("#test1", "test").Return(privs["test"], true),
s.st.EXPECT().IsOn("#test1", "test").Return(&state.ChanPrivs{}, true),
// user1 is known, but not on the channel, so should be associated
s.st.EXPECT().GetNick("user1").Return(nicks["user1"]),
s.st.EXPECT().GetNick("user1").Return(&state.Nick{Nick: "user1"}),
s.st.EXPECT().IsOn("#test1", "user1").Return(nil, false),
s.st.EXPECT().Associate(chan1, nicks["user1"]).Return(privs["user1"]),
s.st.EXPECT().Associate("#test1", "user1").Return(&state.ChanPrivs{}),
s.st.EXPECT().ChannelModes("#test1", "+o", "user1"),
)
for _, n := range []string{"user2", "voice", "halfop", "op", "admin", "owner"} {
gomock.InOrder(
for n, m := range map[string]string{
"user2": "",
"voice": "+v",
"halfop": "+h",
"op": "+o",
"admin": "+a",
"owner": "+q",
} {
calls := []*gomock.Call{
s.st.EXPECT().GetNick(n).Return(nil),
s.st.EXPECT().NewNick(n).Return(nicks[n]),
s.st.EXPECT().NewNick(n).Return(&state.Nick{Nick: n}),
s.st.EXPECT().IsOn("#test1", n).Return(nil, false),
s.st.EXPECT().Associate(chan1, nicks[n]).Return(privs[n]),
)
s.st.EXPECT().Associate("#test1", n).Return(&state.ChanPrivs{}),
}
if m != "" {
calls = append(calls, s.st.EXPECT().ChannelModes("#test1", m, n))
}
gomock.InOrder(calls...)
}
// Send a couple of names replies (complete with trailing space)
c.h_353(ParseLine(":irc.server.org 353 test = #test1 :test @user1 user2 +voice "))
c.h_353(ParseLine(":irc.server.org 353 test = #test1 :%halfop @op &admin ~owner "))
if p := privs["user2"]; p.Voice || p.HalfOp || p.Op || p.Admin || p.Owner {
t.Errorf("353 handler incorrectly set modes on nick.")
}
if !privs["user1"].Op || !privs["voice"].Voice || !privs["halfop"].HalfOp ||
!privs["op"].Op || !privs["admin"].Admin || !privs["owner"].Owner {
t.Errorf("353 handler failed to set correct modes for nicks.")
}
// Check error paths -- send 353 for an unknown channel
s.st.EXPECT().GetChannel("#test2").Return(nil)
c.h_353(ParseLine(":irc.server.org 353 test = #test2 :test ~user3"))
@ -502,21 +438,13 @@ func Test671(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
// Create user1, who should not be secure
nick1 := state.NewNick("user1")
if nick1.Modes.SSL {
t.Errorf("Test nick user1 is already using SSL?")
}
// Send a 671 reply
s.st.EXPECT().GetNick("user1").Return(nick1)
// Ensure 671 reply calls NickModes
gomock.InOrder(
s.st.EXPECT().GetNick("user1").Return(&state.Nick{Nick: "user1"}),
s.st.EXPECT().NickModes("user1", "+z"),
)
c.h_671(ParseLine(":irc.server.org 671 test user1 :some ignored text"))
// Ensure user1 is now known to be on an SSL connection
if !nick1.Modes.SSL {
t.Errorf("Test nick user1 not using SSL?")
}
// Check error paths -- send a 671 for an unknown nick
s.st.EXPECT().GetNick("user2").Return(nil)
c.h_671(ParseLine(":irc.server.org 671 test user2 :some ignored text"))

View file

@ -51,44 +51,41 @@ func (conn *Conn) h_JOIN(line *Line) {
if ch == nil {
// first we've seen of this channel, so should be us joining it
// NOTE this will also take care of nk == nil && ch == nil
if nk != conn.cfg.Me {
if !conn.Me().Equals(nk) {
logging.Warn("irc.JOIN(): JOIN to unknown channel %s received "+
"from (non-me) nick %s", line.Args[0], line.Nick)
return
}
ch = conn.st.NewChannel(line.Args[0])
conn.st.NewChannel(line.Args[0])
// since we don't know much about this channel, ask server for info
// we get the channel users automatically in 353 and the channel
// topic in 332 on join, so we just need to get the modes
conn.Mode(ch.Name)
conn.Mode(line.Args[0])
// sending a WHO for the channel is MUCH more efficient than
// triggering a WHOIS on every nick from the 353 handler
conn.Who(ch.Name)
conn.Who(line.Args[0])
}
if nk == nil {
// this is the first we've seen of this nick
nk = conn.st.NewNick(line.Nick)
nk.Ident = line.Ident
nk.Host = line.Host
conn.st.NewNick(line.Nick)
conn.st.NickInfo(line.Nick, line.Ident, line.Host, "")
// since we don't know much about this nick, ask server for info
conn.Who(nk.Nick)
conn.Who(line.Nick)
}
// this takes care of both nick and channel linking \o/
conn.st.Associate(ch, nk)
conn.st.Associate(line.Args[0], line.Nick)
}
// Handle PARTs from channels to maintain state
func (conn *Conn) h_PART(line *Line) {
conn.st.Dissociate(conn.st.GetChannel(line.Args[0]),
conn.st.GetNick(line.Nick))
conn.st.Dissociate(line.Args[0], line.Nick)
}
// Handle KICKs from channels to maintain state
func (conn *Conn) h_KICK(line *Line) {
// XXX: this won't handle autorejoining channels on KICK
// it's trivial to do this in a seperate handler...
conn.st.Dissociate(conn.st.GetChannel(line.Args[0]),
conn.st.GetNick(line.Args[1]))
conn.st.Dissociate(line.Args[0], line.Args[1])
}
// Handle other people's QUITs
@ -100,15 +97,15 @@ func (conn *Conn) h_QUIT(line *Line) {
func (conn *Conn) h_MODE(line *Line) {
if ch := conn.st.GetChannel(line.Args[0]); ch != nil {
// channel modes first
ch.ParseModes(line.Args[1], line.Args[2:]...)
conn.st.ChannelModes(line.Args[0], line.Args[1], line.Args[2:]...)
} else if nk := conn.st.GetNick(line.Args[0]); nk != nil {
// nick mode change, should be us
if nk != conn.cfg.Me {
if !conn.Me().Equals(nk) {
logging.Warn("irc.MODE(): recieved MODE %s for (non-me) nick %s",
line.Args[1], line.Args[0])
return
}
nk.ParseModes(line.Args[1])
conn.st.NickModes(line.Args[0], line.Args[1])
} else {
logging.Warn("irc.MODE(): not sure what to do with MODE %s",
strings.Join(line.Args, " "))
@ -118,7 +115,7 @@ func (conn *Conn) h_MODE(line *Line) {
// Handle TOPIC changes for channels
func (conn *Conn) h_TOPIC(line *Line) {
if ch := conn.st.GetChannel(line.Args[0]); ch != nil {
ch.Topic = line.Args[1]
conn.st.Topic(line.Args[0], line.Args[1])
} else {
logging.Warn("irc.TOPIC(): topic change on unknown channel %s",
line.Args[0])
@ -127,10 +124,8 @@ func (conn *Conn) h_TOPIC(line *Line) {
// Handle 311 whois reply
func (conn *Conn) h_311(line *Line) {
if nk := conn.st.GetNick(line.Args[1]); nk != nil && nk != conn.Me() {
nk.Ident = line.Args[2]
nk.Host = line.Args[3]
nk.Name = line.Args[5]
if nk := conn.st.GetNick(line.Args[1]); nk != nil && !conn.Me().Equals(nk) {
conn.st.NickInfo(line.Args[1], line.Args[2], line.Args[3], line.Args[5])
} else {
logging.Warn("irc.311(): received WHOIS info for unknown nick %s",
line.Args[1])
@ -140,7 +135,7 @@ func (conn *Conn) h_311(line *Line) {
// Handle 324 mode reply
func (conn *Conn) h_324(line *Line) {
if ch := conn.st.GetChannel(line.Args[1]); ch != nil {
ch.ParseModes(line.Args[2], line.Args[3:]...)
conn.st.ChannelModes(line.Args[1], line.Args[2], line.Args[3:]...)
} else {
logging.Warn("irc.324(): received MODE settings for unknown channel %s",
line.Args[1])
@ -150,7 +145,7 @@ func (conn *Conn) h_324(line *Line) {
// Handle 332 topic reply on join to channel
func (conn *Conn) h_332(line *Line) {
if ch := conn.st.GetChannel(line.Args[1]); ch != nil {
ch.Topic = line.Args[2]
conn.st.Topic(line.Args[1], line.Args[2])
} else {
logging.Warn("irc.332(): received TOPIC value for unknown channel %s",
line.Args[1])
@ -165,24 +160,22 @@ func (conn *Conn) h_352(line *Line) {
line.Args[5])
return
}
if nk == conn.Me() {
if conn.Me().Equals(nk) {
return
}
nk.Ident = line.Args[2]
nk.Host = line.Args[3]
// XXX: do we care about the actual server the nick is on?
// or the hop count to this server?
// last arg contains "<hop count> <real name>"
a := strings.SplitN(line.Args[len(line.Args)-1], " ", 2)
nk.Name = a[1]
conn.st.NickInfo(nk.Nick, line.Args[2], line.Args[3], a[1])
if idx := strings.Index(line.Args[6], "*"); idx != -1 {
nk.Modes.Oper = true
conn.st.NickModes(nk.Nick, "+o")
}
if idx := strings.Index(line.Args[6], "B"); idx != -1 {
nk.Modes.Bot = true
conn.st.NickModes(nk.Nick, "+B")
}
if idx := strings.Index(line.Args[6], "H"); idx != -1 {
nk.Modes.Invisible = true
conn.st.NickModes(nk.Nick, "+i")
}
}
@ -200,27 +193,25 @@ func (conn *Conn) h_353(line *Line) {
nick = nick[1:]
fallthrough
default:
nk := conn.st.GetNick(nick)
if nk == nil {
if conn.st.GetNick(nick) == nil {
// we don't know this nick yet!
nk = conn.st.NewNick(nick)
conn.st.NewNick(nick)
}
cp, ok := conn.st.IsOn(ch.Name, nick)
if !ok {
if _, ok := conn.st.IsOn(ch.Name, nick); !ok {
// This nick isn't associated with this channel yet!
cp = conn.st.Associate(ch, nk)
conn.st.Associate(ch.Name, nick)
}
switch c {
case '~':
cp.Owner = true
conn.st.ChannelModes(ch.Name, "+q", nick)
case '&':
cp.Admin = true
conn.st.ChannelModes(ch.Name, "+a", nick)
case '@':
cp.Op = true
conn.st.ChannelModes(ch.Name, "+o", nick)
case '%':
cp.HalfOp = true
conn.st.ChannelModes(ch.Name, "+h", nick)
case '+':
cp.Voice = true
conn.st.ChannelModes(ch.Name, "+v", nick)
}
}
}
@ -233,7 +224,7 @@ func (conn *Conn) h_353(line *Line) {
// Handle 671 whois reply (nick connected via SSL)
func (conn *Conn) h_671(line *Line) {
if nk := conn.st.GetNick(line.Args[1]); nk != nil {
nk.Modes.SSL = true
conn.st.NickModes(nk.Nick, "+z")
} else {
logging.Warn("irc.671(): received WHOIS SSL info for unknown nick %s",
line.Args[1])