forked from fluffle/goirc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
line.go
144 lines (133 loc) · 3.98 KB
/
line.go
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
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
Time time.Time
}
// Copy() returns a deep copy of the Line.
func (l *Line) Copy() *Line {
nl := *l
nl.Args = make([]string, len(l.Args))
copy(nl.Args, l.Args)
return &nl
}
// 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:
if !line.Public() {
return line.Nick
}
case CTCP, CTCPREPLY:
if !line.Public() {
return line.Nick
}
return line.Args[1]
}
if len(line.Args) > 0 {
return line.Args[0]
}
return ""
}
// 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
}
// ParseLine() creates a Line from an incoming message from the IRC server.
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 {
line.Src, s = s[1:idx], s[idx+1:]
} else {
// pretty sure we shouldn't get here ...
return nil
}
// 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 {
line.Nick = line.Src[:nidx]
line.Ident = line.Src[nidx+1 : uidx]
line.Host = line.Src[uidx+1:]
}
}
// now we're here, we've parsed a :nick!user@host or :server off
// s should contain "cmd args[] :text"
args := strings.SplitN(s, " :", 2)
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 {
line.Args = args[1:]
}
// So, I think CTCP and (in particular) CTCP ACTION are better handled as
// separate events as opposed to forcing people to have gargantuan
// handlers to cope with the possibilities.
if (line.Cmd == PRIVMSG || line.Cmd == NOTICE) &&
len(line.Args[1]) > 2 &&
strings.HasPrefix(line.Args[1], "\001") &&
strings.HasSuffix(line.Args[1], "\001") {
// WOO, it's a CTCP message
t := strings.SplitN(strings.Trim(line.Args[1], "\001"), " ", 2)
if len(t) > 1 {
// Replace the line with the unwrapped CTCP
line.Args[1] = t[1]
}
if c := strings.ToUpper(t[0]); c == ACTION && line.Cmd == PRIVMSG {
// make a CTCP ACTION it's own event a-la PRIVMSG
line.Cmd = c
} else {
// otherwise, dispatch a generic CTCP/CTCPREPLY event that
// contains the type of CTCP in line.Args[0]
if line.Cmd == PRIVMSG {
line.Cmd = CTCP
} else {
line.Cmd = CTCPREPLY
}
line.Args = append([]string{c}, line.Args...)
}
}
return line
}