summaryrefslogtreecommitdiff
path: root/vendor/github.com/fluffle/goirc/client/state_handlers.go
blob: 847679c5a0c488ee339678c9997c23368d4dc18a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
package client

// this file contains the extra set of event handlers
// to manage tracking state for an IRC connection

import (
	"strings"

	"github.com/fluffle/goirc/logging"
)

var stHandlers = map[string]HandlerFunc{
	"JOIN":  (*Conn).h_JOIN,
	"KICK":  (*Conn).h_KICK,
	"MODE":  (*Conn).h_MODE,
	"NICK":  (*Conn).h_STNICK,
	"PART":  (*Conn).h_PART,
	"QUIT":  (*Conn).h_QUIT,
	"TOPIC": (*Conn).h_TOPIC,
	"311":   (*Conn).h_311,
	"324":   (*Conn).h_324,
	"332":   (*Conn).h_332,
	"352":   (*Conn).h_352,
	"353":   (*Conn).h_353,
	"671":   (*Conn).h_671,
}

func (conn *Conn) addSTHandlers() {
	for n, h := range stHandlers {
		conn.stRemovers = append(conn.stRemovers, conn.handle(n, h))
	}
}

func (conn *Conn) delSTHandlers() {
	for _, h := range conn.stRemovers {
		h.Remove()
	}
	conn.stRemovers = conn.stRemovers[:0]
}

// Handle NICK messages that need to update the state tracker
func (conn *Conn) h_STNICK(line *Line) {
	// all nicks should be handled the same way, our own included
	conn.st.ReNick(line.Nick, line.Args[0])
}

// Handle JOINs to channels to maintain state
func (conn *Conn) h_JOIN(line *Line) {
	ch := conn.st.GetChannel(line.Args[0])
	nk := conn.st.GetNick(line.Nick)
	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 !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
		}
		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(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(line.Args[0])
	}
	if nk == nil {
		// this is the first we've seen of this nick
		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(line.Nick)
	}
	// this takes care of both nick and channel linking \o/
	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(line.Args[0], line.Nick)
}

// Handle KICKs from channels to maintain state
func (conn *Conn) h_KICK(line *Line) {
	if !line.argslen(1) {
		return
	}
	// XXX: this won't handle autorejoining channels on KICK
	// it's trivial to do this in a seperate handler...
	conn.st.Dissociate(line.Args[0], line.Args[1])
}

// Handle other people's QUITs
func (conn *Conn) h_QUIT(line *Line) {
	conn.st.DelNick(line.Nick)
}

// Handle MODE changes for channels we know about (and our nick personally)
func (conn *Conn) h_MODE(line *Line) {
	if !line.argslen(1) {
		return
	}
	if ch := conn.st.GetChannel(line.Args[0]); ch != nil {
		// channel modes first
		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 !conn.Me().Equals(nk) {
			logging.Warn("irc.MODE(): recieved MODE %s for (non-me) nick %s",
				line.Args[1], line.Args[0])
			return
		}
		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, " "))
	}
}

// Handle TOPIC changes for channels
func (conn *Conn) h_TOPIC(line *Line) {
	if !line.argslen(1) {
		return
	}
	if ch := conn.st.GetChannel(line.Args[0]); ch != nil {
		conn.st.Topic(line.Args[0], line.Args[1])
	} else {
		logging.Warn("irc.TOPIC(): topic change on unknown channel %s",
			line.Args[0])
	}
}

// Handle 311 whois reply
func (conn *Conn) h_311(line *Line) {
	if !line.argslen(5) {
		return
	}
	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])
	}
}

// Handle 324 mode reply
func (conn *Conn) h_324(line *Line) {
	if !line.argslen(2) {
		return
	}
	if ch := conn.st.GetChannel(line.Args[1]); ch != nil {
		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])
	}
}

// Handle 332 topic reply on join to channel
func (conn *Conn) h_332(line *Line) {
	if !line.argslen(2) {
		return
	}
	if ch := conn.st.GetChannel(line.Args[1]); ch != nil {
		conn.st.Topic(line.Args[1], line.Args[2])
	} else {
		logging.Warn("irc.332(): received TOPIC value for unknown channel %s",
			line.Args[1])
	}
}

// Handle 352 who reply
func (conn *Conn) h_352(line *Line) {
	if !line.argslen(5) {
		return
	}
	nk := conn.st.GetNick(line.Args[5])
	if nk == nil {
		logging.Warn("irc.352(): received WHO reply for unknown nick %s",
			line.Args[5])
		return
	}
	if conn.Me().Equals(nk) {
		return
	}
	// 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)
	conn.st.NickInfo(nk.Nick, line.Args[2], line.Args[3], a[1])
	if !line.argslen(6) {
		return
	}
	if idx := strings.Index(line.Args[6], "*"); idx != -1 {
		conn.st.NickModes(nk.Nick, "+o")
	}
	if idx := strings.Index(line.Args[6], "B"); idx != -1 {
		conn.st.NickModes(nk.Nick, "+B")
	}
	if idx := strings.Index(line.Args[6], "H"); idx != -1 {
		conn.st.NickModes(nk.Nick, "+i")
	}
}

// Handle 353 names reply
func (conn *Conn) h_353(line *Line) {
	if !line.argslen(2) {
		return
	}
	if ch := conn.st.GetChannel(line.Args[2]); ch != nil {
		nicks := strings.Split(line.Args[len(line.Args)-1], " ")
		for _, nick := range nicks {
			// UnrealIRCd's coders are lazy and leave a trailing space
			if nick == "" {
				continue
			}
			switch c := nick[0]; c {
			case '~', '&', '@', '%', '+':
				nick = nick[1:]
				fallthrough
			default:
				if conn.st.GetNick(nick) == nil {
					// we don't know this nick yet!
					conn.st.NewNick(nick)
				}
				if _, ok := conn.st.IsOn(ch.Name, nick); !ok {
					// This nick isn't associated with this channel yet!
					conn.st.Associate(ch.Name, nick)
				}
				switch c {
				case '~':
					conn.st.ChannelModes(ch.Name, "+q", nick)
				case '&':
					conn.st.ChannelModes(ch.Name, "+a", nick)
				case '@':
					conn.st.ChannelModes(ch.Name, "+o", nick)
				case '%':
					conn.st.ChannelModes(ch.Name, "+h", nick)
				case '+':
					conn.st.ChannelModes(ch.Name, "+v", nick)
				}
			}
		}
	} else {
		logging.Warn("irc.353(): received NAMES list for unknown channel %s",
			line.Args[2])
	}
}

// Handle 671 whois reply (nick connected via SSL)
func (conn *Conn) h_671(line *Line) {
	if !line.argslen(1) {
		return
	}
	if nk := conn.st.GetNick(line.Args[1]); nk != nil {
		conn.st.NickModes(nk.Nick, "+z")
	} else {
		logging.Warn("irc.671(): received WHOIS SSL info for unknown nick %s",
			line.Args[1])
	}
}