2011-07-22 00:11:15 +00:00
|
|
|
package client
|
|
|
|
|
|
|
|
import (
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
// We parse an incoming line into this struct. Line.Cmd is used as the trigger
|
|
|
|
// name for incoming event handlers, see *Conn.recv() for details.
|
|
|
|
// Raw =~ ":nick!user@host cmd args[] :text"
|
|
|
|
// Src == "nick!user@host"
|
|
|
|
// Cmd == e.g. PRIVMSG, 332
|
|
|
|
type Line struct {
|
|
|
|
Nick, Ident, Host, Src string
|
|
|
|
Cmd, Raw string
|
|
|
|
Args []string
|
2012-02-04 00:51:06 +00:00
|
|
|
Time time.Time
|
2011-07-22 00:11:15 +00:00
|
|
|
}
|
|
|
|
|
2013-03-10 15:54:37 +00:00
|
|
|
// Copy() returns a deep copy of the Line.
|
2011-07-28 16:56:14 +00:00
|
|
|
func (l *Line) Copy() *Line {
|
2011-07-28 17:01:46 +00:00
|
|
|
nl := *l
|
|
|
|
nl.Args = make([]string, len(l.Args))
|
|
|
|
copy(nl.Args, l.Args)
|
2011-07-28 16:56:14 +00:00
|
|
|
return &nl
|
|
|
|
}
|
|
|
|
|
2013-03-10 15:55:16 +00:00
|
|
|
// Return the contents of the text portion of a line. This only really
|
|
|
|
// makes sense for lines with a :text part, but there are a lot of them.
|
|
|
|
func (line *Line) Text() string {
|
|
|
|
if len(line.Args) > 0 {
|
|
|
|
return line.Args[len(line.Args)-1]
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return the target of the line, usually the first Arg for the IRC verb.
|
|
|
|
// If the line was broadcast from a channel, the target will be that channel.
|
|
|
|
// If the line was broadcast by a user, the target will be that user.
|
|
|
|
// TODO(fluffle): Add 005 CHANTYPES parsing for this?
|
|
|
|
func (line *Line) Target() string {
|
|
|
|
switch line.Cmd {
|
|
|
|
case PRIVMSG, NOTICE, ACTION:
|
2013-03-17 16:46:39 +00:00
|
|
|
if !line.Public() {
|
2013-03-10 15:55:16 +00:00
|
|
|
return line.Nick
|
|
|
|
}
|
|
|
|
case CTCP, CTCPREPLY:
|
2013-03-17 16:46:39 +00:00
|
|
|
if !line.Public() {
|
2013-03-10 15:55:16 +00:00
|
|
|
return line.Nick
|
|
|
|
}
|
|
|
|
return line.Args[1]
|
|
|
|
}
|
|
|
|
if len(line.Args) > 0 {
|
|
|
|
return line.Args[0]
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2013-03-17 16:46:39 +00:00
|
|
|
// NOTE: Makes the assumption that all channels start with #.
|
|
|
|
func (line *Line) Public() bool {
|
|
|
|
switch line.Cmd {
|
|
|
|
case PRIVMSG, NOTICE, ACTION:
|
|
|
|
if strings.HasPrefix(line.Args[0], "#") {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
case CTCP, CTCPREPLY:
|
|
|
|
// CTCP prepends the CTCP verb to line.Args, thus for the message
|
|
|
|
// :nick!user@host PRIVMSG #foo :\001BAR baz\001
|
|
|
|
// line.Args contains: []string{"BAR", "#foo", "baz"}
|
|
|
|
// TODO(fluffle): Arguably this is broken, and we should have
|
|
|
|
// line.Args containing: []string{"#foo", "BAR", "baz"}
|
|
|
|
// ... OR change conn.Ctcp()'s argument order to be consistent.
|
|
|
|
if strings.HasPrefix(line.Args[1], "#") {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-03-10 15:54:37 +00:00
|
|
|
// parseLine() creates a Line from an incoming message from the IRC server.
|
2011-07-22 00:11:15 +00:00
|
|
|
func parseLine(s string) *Line {
|
|
|
|
line := &Line{Raw: s}
|
|
|
|
if s[0] == ':' {
|
|
|
|
// remove a source and parse it
|
|
|
|
if idx := strings.Index(s, " "); idx != -1 {
|
2011-07-25 22:02:57 +00:00
|
|
|
line.Src, s = s[1:idx], s[idx+1:]
|
2011-07-22 00:11:15 +00:00
|
|
|
} else {
|
|
|
|
// pretty sure we shouldn't get here ...
|
2011-11-06 04:56:46 +00:00
|
|
|
return nil
|
2011-07-22 00:11:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// src can be the hostname of the irc server or a nick!user@host
|
|
|
|
line.Host = line.Src
|
|
|
|
nidx, uidx := strings.Index(line.Src, "!"), strings.Index(line.Src, "@")
|
|
|
|
if uidx != -1 && nidx != -1 {
|
2011-07-25 22:02:57 +00:00
|
|
|
line.Nick = line.Src[:nidx]
|
2011-08-03 07:03:08 +00:00
|
|
|
line.Ident = line.Src[nidx+1 : uidx]
|
2011-07-25 22:02:57 +00:00
|
|
|
line.Host = line.Src[uidx+1:]
|
2011-07-22 00:11:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// now we're here, we've parsed a :nick!user@host or :server off
|
|
|
|
// s should contain "cmd args[] :text"
|
2011-08-03 07:04:01 +00:00
|
|
|
args := strings.SplitN(s, " :", 2)
|
2011-07-22 00:11:15 +00:00
|
|
|
if len(args) > 1 {
|
|
|
|
args = append(strings.Fields(args[0]), args[1])
|
|
|
|
} else {
|
|
|
|
args = strings.Fields(args[0])
|
|
|
|
}
|
|
|
|
line.Cmd = strings.ToUpper(args[0])
|
|
|
|
if len(args) > 1 {
|
2011-07-25 22:02:57 +00:00
|
|
|
line.Args = args[1:]
|
2011-07-22 00:11:15 +00:00
|
|
|
}
|
2011-07-27 20:10:37 +00:00
|
|
|
|
|
|
|
// So, I think CTCP and (in particular) CTCP ACTION are better handled as
|
2011-08-22 22:21:30 +00:00
|
|
|
// separate events as opposed to forcing people to have gargantuan
|
2011-07-27 20:10:37 +00:00
|
|
|
// handlers to cope with the possibilities.
|
2013-03-10 15:54:37 +00:00
|
|
|
if (line.Cmd == PRIVMSG || line.Cmd == NOTICE) &&
|
2011-07-27 20:10:37 +00:00
|
|
|
len(line.Args[1]) > 2 &&
|
|
|
|
strings.HasPrefix(line.Args[1], "\001") &&
|
|
|
|
strings.HasSuffix(line.Args[1], "\001") {
|
|
|
|
// WOO, it's a CTCP message
|
2011-08-03 07:03:08 +00:00
|
|
|
t := strings.SplitN(strings.Trim(line.Args[1], "\001"), " ", 2)
|
2011-07-27 20:10:37 +00:00
|
|
|
if len(t) > 1 {
|
|
|
|
// Replace the line with the unwrapped CTCP
|
|
|
|
line.Args[1] = t[1]
|
|
|
|
}
|
2013-03-10 15:54:37 +00:00
|
|
|
if c := strings.ToUpper(t[0]); c == ACTION && line.Cmd == PRIVMSG {
|
2011-07-27 20:10:37 +00:00
|
|
|
// make a CTCP ACTION it's own event a-la PRIVMSG
|
|
|
|
line.Cmd = c
|
|
|
|
} else {
|
2011-08-22 22:21:30 +00:00
|
|
|
// otherwise, dispatch a generic CTCP/CTCPREPLY event that
|
2011-07-27 20:10:37 +00:00
|
|
|
// contains the type of CTCP in line.Args[0]
|
2013-03-10 15:54:37 +00:00
|
|
|
if line.Cmd == PRIVMSG {
|
|
|
|
line.Cmd = CTCP
|
2011-08-22 22:21:30 +00:00
|
|
|
} else {
|
2013-03-10 15:54:37 +00:00
|
|
|
line.Cmd = CTCPREPLY
|
2011-08-22 22:21:30 +00:00
|
|
|
}
|
2011-07-27 20:10:37 +00:00
|
|
|
line.Args = append([]string{c}, line.Args...)
|
|
|
|
}
|
|
|
|
}
|
2011-07-22 00:11:15 +00:00
|
|
|
return line
|
|
|
|
}
|