summaryrefslogtreecommitdiff
path: root/vendor/github.com/fluffle/goirc/client/commands.go
blob: 101c7d3bd0bdc1ff897fe4a9284809ac7135331b (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
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
package client

import (
	"fmt"
	"strings"
)

const (
	REGISTER     = "REGISTER"
	CONNECTED    = "CONNECTED"
	DISCONNECTED = "DISCONNECTED"
	ACTION       = "ACTION"
	AWAY         = "AWAY"
	CAP          = "CAP"
	CTCP         = "CTCP"
	CTCPREPLY    = "CTCPREPLY"
	ERROR        = "ERROR"
	INVITE       = "INVITE"
	JOIN         = "JOIN"
	KICK         = "KICK"
	MODE         = "MODE"
	NICK         = "NICK"
	NOTICE       = "NOTICE"
	OPER         = "OPER"
	PART         = "PART"
	PASS         = "PASS"
	PING         = "PING"
	PONG         = "PONG"
	PRIVMSG      = "PRIVMSG"
	QUIT         = "QUIT"
	TOPIC        = "TOPIC"
	USER         = "USER"
	VERSION      = "VERSION"
	VHOST        = "VHOST"
	WHO          = "WHO"
	WHOIS        = "WHOIS"
	defaultSplit = 450
)

// cutNewLines() pares down a string to the part before the first "\r" or "\n".
func cutNewLines(s string) string {
	r := strings.SplitN(s, "\r", 2)
	r = strings.SplitN(r[0], "\n", 2)
	return r[0]
}

// indexFragment looks for the last sentence split-point (defined as one of
// the punctuation characters .:;,!?"' followed by a space) in the string s
// and returns the index in the string after that split-point. If no split-
// point is found it returns the index after the last space in s, or -1.
func indexFragment(s string) int {
	max := -1
	for _, sep := range []string{". ", ": ", "; ", ", ", "! ", "? ", "\" ", "' "} {
		if idx := strings.LastIndex(s, sep); idx > max {
			max = idx
		}
	}
	if max > 0 {
		return max + 2
	}
	if idx := strings.LastIndex(s, " "); idx > 0 {
		return idx + 1
	}
	return -1
}

// splitMessage splits a message > splitLen chars at:
//   1. the end of the last sentence fragment before splitLen
//   2. the end of the last word before splitLen
//   3. splitLen itself
func splitMessage(msg string, splitLen int) (msgs []string) {
	// This is quite short ;-)
	if splitLen < 13 {
		splitLen = defaultSplit
	}
	for len(msg) > splitLen {
		idx := indexFragment(msg[:splitLen-3])
		if idx < 0 {
			idx = splitLen - 3
		}
		msgs = append(msgs, msg[:idx]+"...")
		msg = msg[idx:]
	}
	return append(msgs, msg)
}

// Raw sends a raw line to the server, should really only be used for
// debugging purposes but may well come in handy.
func (conn *Conn) Raw(rawline string) {
	// Avoid command injection by enforcing one command per line.
	conn.out <- cutNewLines(rawline)
}

// Pass sends a PASS command to the server.
//     PASS password
func (conn *Conn) Pass(password string) { conn.Raw(PASS + " " + password) }

// Nick sends a NICK command to the server.
//     NICK nick
func (conn *Conn) Nick(nick string) { conn.Raw(NICK + " " + nick) }

// User sends a USER command to the server.
//     USER ident 12 * :name
func (conn *Conn) User(ident, name string) {
	conn.Raw(USER + " " + ident + " 12 * :" + name)
}

// Join sends a JOIN command to the server with an optional key.
//     JOIN channel [key]
func (conn *Conn) Join(channel string, key ...string) {
	k := ""
	if len(key) > 0 {
		k = " " + key[0]
	}
	conn.Raw(JOIN + " " + channel + k)
}

// Part sends a PART command to the server with an optional part message.
//     PART channel [:message]
func (conn *Conn) Part(channel string, message ...string) {
	msg := strings.Join(message, " ")
	if msg != "" {
		msg = " :" + msg
	}
	conn.Raw(PART + " " + channel + msg)
}

// Kick sends a KICK command to remove a nick from a channel.
//     KICK channel nick [:message]
func (conn *Conn) Kick(channel, nick string, message ...string) {
	msg := strings.Join(message, " ")
	if msg != "" {
		msg = " :" + msg
	}
	conn.Raw(KICK + " " + channel + " " + nick + msg)
}

// Quit sends a QUIT command to the server with an optional quit message.
//     QUIT [:message]
func (conn *Conn) Quit(message ...string) {
	msg := strings.Join(message, " ")
	if msg == "" {
		msg = conn.cfg.QuitMessage
	}
	conn.Raw(QUIT + " :" + msg)
}

// Whois sends a WHOIS command to the server.
//     WHOIS nick
func (conn *Conn) Whois(nick string) { conn.Raw(WHOIS + " " + nick) }

// Who sends a WHO command to the server.
//     WHO nick
func (conn *Conn) Who(nick string) { conn.Raw(WHO + " " + nick) }

// Privmsg sends a PRIVMSG to the target nick or channel t.
// If msg is longer than Config.SplitLen characters, multiple PRIVMSGs
// will be sent to the target containing sequential parts of msg.
// PRIVMSG t :msg
func (conn *Conn) Privmsg(t, msg string) {
	prefix := PRIVMSG + " " + t + " :"
	for _, s := range splitMessage(msg, conn.cfg.SplitLen) {
		conn.Raw(prefix + s)
	}
}

// Privmsgln is the variadic version of Privmsg that formats the message
// that is sent to the target nick or channel t using the
// fmt.Sprintln function.
// Note: Privmsgln doesn't add the '\n' character at the end of the message.
func (conn *Conn) Privmsgln(t string, a ...interface{}) {
	msg := fmt.Sprintln(a...)
	// trimming the new-line character added by the fmt.Sprintln function,
	// since it's irrelevant.
	msg = msg[:len(msg)-1]
	conn.Privmsg(t, msg)
}

// Privmsgf is the variadic version of Privmsg that formats the message
// that is sent to the target nick or channel t using the
// fmt.Sprintf function.
func (conn *Conn) Privmsgf(t, format string, a ...interface{}) {
	msg := fmt.Sprintf(format, a...)
	conn.Privmsg(t, msg)
}

// Notice sends a NOTICE to the target nick or channel t.
// If msg is longer than Config.SplitLen characters, multiple NOTICEs
// will be sent to the target containing sequential parts of msg.
//     NOTICE t :msg
func (conn *Conn) Notice(t, msg string) {
	for _, s := range splitMessage(msg, conn.cfg.SplitLen) {
		conn.Raw(NOTICE + " " + t + " :" + s)
	}
}

// Ctcp sends a (generic) CTCP message to the target nick
// or channel t, with an optional argument.
//     PRIVMSG t :\001CTCP arg\001
func (conn *Conn) Ctcp(t, ctcp string, arg ...string) {
	// We need to split again here to ensure
	for _, s := range splitMessage(strings.Join(arg, " "), conn.cfg.SplitLen) {
		if s != "" {
			s = " " + s
		}
		// Using Raw rather than PRIVMSG here to avoid double-split problems.
		conn.Raw(PRIVMSG + " " + t + " :\001" + strings.ToUpper(ctcp) + s + "\001")
	}
}

// CtcpReply sends a (generic) CTCP reply to the target nick
// or channel t, with an optional argument.
//     NOTICE t :\001CTCP arg\001
func (conn *Conn) CtcpReply(t, ctcp string, arg ...string) {
	for _, s := range splitMessage(strings.Join(arg, " "), conn.cfg.SplitLen) {
		if s != "" {
			s = " " + s
		}
		// Using Raw rather than NOTICE here to avoid double-split problems.
		conn.Raw(NOTICE + " " + t + " :\001" + strings.ToUpper(ctcp) + s + "\001")
	}
}

// Version sends a CTCP "VERSION" to the target nick or channel t.
func (conn *Conn) Version(t string) { conn.Ctcp(t, VERSION) }

// Action sends a CTCP "ACTION" to the target nick or channel t.
func (conn *Conn) Action(t, msg string) { conn.Ctcp(t, ACTION, msg) }

// Topic() sends a TOPIC command for a channel.
// If no topic is provided this requests that a 332 response is sent by the
// server for that channel, which can then be handled to retrieve the current
// channel topic. If a topic is provided the channel's topic will be set.
//     TOPIC channel
//     TOPIC channel :topic
func (conn *Conn) Topic(channel string, topic ...string) {
	t := strings.Join(topic, " ")
	if t != "" {
		t = " :" + t
	}
	conn.Raw(TOPIC + " " + channel + t)
}

// Mode sends a MODE command for a target nick or channel t.
// If no mode strings are provided this requests that a 324 response is sent
// by the server for the target. Otherwise the mode strings are concatenated
// with spaces and sent to the server. This allows e.g.
//     conn.Mode("#channel", "+nsk", "mykey")
//
//     MODE t
//     MODE t modestring
func (conn *Conn) Mode(t string, modestring ...string) {
	mode := strings.Join(modestring, " ")
	if mode != "" {
		mode = " " + mode
	}
	conn.Raw(MODE + " " + t + mode)
}

// Away sends an AWAY command to the server.
// If a message is provided it sets the client's away status with that message,
// otherwise it resets the client's away status.
//     AWAY
//     AWAY :message
func (conn *Conn) Away(message ...string) {
	msg := strings.Join(message, " ")
	if msg != "" {
		msg = " :" + msg
	}
	conn.Raw(AWAY + msg)
}

// Invite sends an INVITE command to the server.
//     INVITE nick channel
func (conn *Conn) Invite(nick, channel string) {
	conn.Raw(INVITE + " " + nick + " " + channel)
}

// Oper sends an OPER command to the server.
//     OPER user pass
func (conn *Conn) Oper(user, pass string) { conn.Raw(OPER + " " + user + " " + pass) }

// VHost sends a VHOST command to the server.
//     VHOST user pass
func (conn *Conn) VHost(user, pass string) { conn.Raw(VHOST + " " + user + " " + pass) }

// Ping sends a PING command to the server, which should PONG.
//     PING :message
func (conn *Conn) Ping(message string) { conn.Raw(PING + " :" + message) }

// Pong sends a PONG command to the server.
//     PONG :message
func (conn *Conn) Pong(message string) { conn.Raw(PONG + " :" + message) }

// Cap sends a CAP command to the server.
//     CAP subcommand
//     CAP subcommand :message
func (conn *Conn) Cap(subcommmand string, capabilities ...string) {
	if len(capabilities) == 0 {
		conn.Raw(CAP + " " + subcommmand)
	} else {
		conn.Raw(CAP + " " + subcommmand + " :" + strings.Join(capabilities, " "))
	}
}