commit c761717
Hubert Hirtz
·
2020-11-21 11:29:51 +0000 UTC
parent 8828195
Document the IRC library
2 files changed,
+86,
-27
+51,
-21
1@@ -42,6 +42,7 @@ func (auth *SASLPlain) Respond(challenge string) (res string, err error) {
2 return
3 }
4
5+// SupportedCapabilities is the set of capabilities supported by this library.
6 var SupportedCapabilities = map[string]struct{}{
7 "account-notify": {},
8 "account-tag": {},
9@@ -61,6 +62,8 @@ var SupportedCapabilities = map[string]struct{}{
10 "userhost-in-names": {},
11 }
12
13+// Values taken by the "@+typing=" client tag. TypingUnspec means the value or
14+// tag is absent.
15 const (
16 TypingUnspec = iota
17 TypingActive
18@@ -68,6 +71,13 @@ const (
19 TypingDone
20 )
21
22+// action contains the arguments of a user action.
23+//
24+// To keep connection reads and writes in a single coroutine, the library
25+// interface functions like Join("#channel") or PrivMsg("target", "message")
26+// don't interact with the IRC session directly. Instead, they push an action
27+// in the action channel. This action is then processed by the correct
28+// coroutine.
29 type action interface{}
30
31 type (
32@@ -105,22 +115,25 @@ type (
33 }
34 )
35
36+// User is a known IRC user (we share a channel with it).
37 type User struct {
38- Name *Prefix
39- AwayMsg string
40+ Name *Prefix // the nick, user and hostname of the user if known.
41+ AwayMsg string // the away message if the user is away, "" otherwise.
42 }
43
44+// Channel is a joined channel.
45 type Channel struct {
46- Name string
47- Members map[*User]string
48- Topic string
49- TopicWho *Prefix
50- TopicTime time.Time
51- Secret bool
52-
53- complete bool
54+ Name string // the name of the channel.
55+ Members map[*User]string // the set of members associated with their membership.
56+ Topic string // the topic of the channel, or "" if absent.
57+ TopicWho *Prefix // the name of the last user who set the topic.
58+ TopicTime time.Time // the last time the topic has been changed.
59+ Secret bool // whether the channel is on the server channel list.
60+
61+ complete bool // whether this stucture is fully initialized.
62 }
63
64+// SessionParams defines how to connect to an IRC server.
65 type SessionParams struct {
66 Nickname string
67 Username string
68@@ -128,24 +141,25 @@ type SessionParams struct {
69
70 Auth SASLClient
71
72- Debug bool
73+ Debug bool // whether the Session should report all messages it sends and receive.
74 }
75
76+// Session is an IRC session/connection/whatever.
77 type Session struct {
78 conn io.ReadWriteCloser
79- msgs chan Message
80- acts chan action
81- evts chan Event
82+ msgs chan Message // incoming messages.
83+ acts chan action // user actions.
84+ evts chan Event // events sent to the user.
85
86 debug bool
87
88 running atomic.Value // bool
89 registered bool
90- typings *Typings
91- typingStamps map[string]time.Time
92+ typings *Typings // incoming typing notifications.
93+ typingStamps map[string]time.Time // user typing instants.
94
95 nick string
96- nickCf string
97+ nickCf string // casemapped nickname.
98 user string
99 real string
100 acct string
101@@ -154,13 +168,18 @@ type Session struct {
102
103 availableCaps map[string]string
104 enabledCaps map[string]struct{}
105- features map[string]string
106+ features map[string]string // server ISUPPORT advertized features.
107
108- users map[string]*User
109- channels map[string]Channel
110- chBatches map[string]HistoryEvent
111+ users map[string]*User // known users.
112+ channels map[string]Channel // joined channels.
113+ chBatches map[string]HistoryEvent // channel history batches being processed.
114 }
115
116+// NewSession starts an IRC session from the given connection and session
117+// parameters.
118+//
119+// It returns an error when the paramaters are invalid, or when it cannot write
120+// to the connection.
121 func NewSession(conn io.ReadWriteCloser, params SessionParams) (*Session, error) {
122 s := &Session{
123 conn: conn,
124@@ -226,10 +245,12 @@ func NewSession(conn io.ReadWriteCloser, params SessionParams) (*Session, error)
125 return s, nil
126 }
127
128+// Running reports whether we are still connected to the server.
129 func (s *Session) Running() bool {
130 return s.running.Load().(bool)
131 }
132
133+// Stop stops the session and closes the connection.
134 func (s *Session) Stop() {
135 if !s.Running() {
136 return
137@@ -241,10 +262,13 @@ func (s *Session) Stop() {
138 close(s.msgs)
139 }
140
141+// Poll returns the event channel where incoming events are reported.
142 func (s *Session) Poll() (events <-chan Event) {
143 return s.evts
144 }
145
146+// HasCapability reports whether the given capability has been negociated
147+// successfully.
148 func (s *Session) HasCapability(capability string) bool {
149 _, ok := s.enabledCaps[capability]
150 return ok
151@@ -254,6 +278,7 @@ func (s *Session) Nick() string {
152 return s.nick
153 }
154
155+// NickCf is our casemapped nickname.
156 func (s *Session) NickCf() string {
157 return s.nickCf
158 }
159@@ -274,6 +299,7 @@ func (s *Session) Casemap(name string) string {
160 }
161 }
162
163+// Users returns the list of all known nicknames.
164 func (s *Session) Users() []string {
165 users := make([]string, 0, len(s.users))
166 for _, u := range s.users {
167@@ -282,6 +308,8 @@ func (s *Session) Users() []string {
168 return users
169 }
170
171+// Names returns the list of users in the given channel, or nil if this channel
172+// is not known by the session.
173 func (s *Session) Names(channel string) []Member {
174 var names []Member
175 if c, ok := s.channels[s.Casemap(channel)]; ok {
176@@ -296,6 +324,7 @@ func (s *Session) Names(channel string) []Member {
177 return names
178 }
179
180+// Typings returns the list of nickname who are currently typing.
181 func (s *Session) Typings(target string) []string {
182 targetCf := s.Casemap(target)
183 var res []string
184@@ -333,6 +362,7 @@ func (s *Session) Topic(channel string) (topic string, who *Prefix, at time.Time
185 return
186 }
187
188+// SendRaw sends its given argument verbatim to the server.
189 func (s *Session) SendRaw(raw string) {
190 s.acts <- actionSendRaw{raw}
191 }
+35,
-6
1@@ -8,6 +8,8 @@ import (
2 "time"
3 )
4
5+// CasemapASCII of name is the canonical representation of name according to the
6+// ascii casemapping.
7 func CasemapASCII(name string) string {
8 var sb strings.Builder
9 sb.Grow(len(name))
10@@ -20,6 +22,8 @@ func CasemapASCII(name string) string {
11 return sb.String()
12 }
13
14+// CasemapASCII of name is the canonical representation of name according to the
15+// rfc-1459 casemapping.
16 func CasemapRFC1459(name string) string {
17 var sb strings.Builder
18 sb.Grow(len(name))
19@@ -40,20 +44,21 @@ func CasemapRFC1459(name string) string {
20 return sb.String()
21 }
22
23-func word(s string) (w, rest string) {
24+// word returns the first word of s and the rest of s.
25+func word(s string) (word, rest string) {
26 split := strings.SplitN(s, " ", 2)
27-
28 if len(split) < 2 {
29- w = split[0]
30+ word = split[0]
31 rest = ""
32 } else {
33- w = split[0]
34+ word = split[0]
35 rest = split[1]
36 }
37-
38 return
39 }
40
41+// tagEscape returns the the value of '\c' given c according to the message-tags
42+// specification.
43 func tagEscape(c rune) (escape rune) {
44 switch c {
45 case ':':
46@@ -67,10 +72,11 @@ func tagEscape(c rune) (escape rune) {
47 default:
48 escape = c
49 }
50-
51 return
52 }
53
54+// unescapeTagValue removes escapes from the given string and replaces them with
55+// their meaningful values.
56 func unescapeTagValue(escaped string) string {
57 var builder strings.Builder
58 builder.Grow(len(escaped))
59@@ -96,6 +102,7 @@ func unescapeTagValue(escaped string) string {
60 return builder.String()
61 }
62
63+// escapeTagValue does the inverse operation of unescapeTagValue.
64 func escapeTagValue(unescaped string) string {
65 var sb strings.Builder
66 sb.Grow(len(unescaped) * 2)
67@@ -163,6 +170,8 @@ type Prefix struct {
68 Host string
69 }
70
71+// ParsePrefix parses a "nick!user@host" combination (or a prefix) from the given
72+// string.
73 func ParsePrefix(s string) (p *Prefix) {
74 if s == "" {
75 return
76@@ -185,6 +194,7 @@ func ParsePrefix(s string) (p *Prefix) {
77 return
78 }
79
80+// Copy makes a copy of the prefix, but doesn't copy the internal strings.
81 func (p *Prefix) Copy() *Prefix {
82 if p == nil {
83 return nil
84@@ -194,6 +204,7 @@ func (p *Prefix) Copy() *Prefix {
85 return res
86 }
87
88+// String returns the "nick!user@host" representation of the prefix.
89 func (p *Prefix) String() string {
90 if p == nil {
91 return ""
92@@ -210,6 +221,7 @@ func (p *Prefix) String() string {
93 }
94 }
95
96+// Message is the representation of an IRC message.
97 type Message struct {
98 Tags map[string]string
99 Prefix *Prefix
100@@ -217,6 +229,8 @@ type Message struct {
101 Params []string
102 }
103
104+// ParseMessage parses the message from the given string, which must be trimmed
105+// of "\r\n" beforehand.
106 func ParseMessage(line string) (msg Message, err error) {
107 line = strings.TrimLeft(line, " ")
108 if line == "" {
109@@ -268,6 +282,7 @@ func ParseMessage(line string) (msg Message, err error) {
110 return
111 }
112
113+// IsReply reports whether the message command is a server reply.
114 func (msg *Message) IsReply() bool {
115 if len(msg.Command) != 3 {
116 return false
117@@ -280,6 +295,8 @@ func (msg *Message) IsReply() bool {
118 return true
119 }
120
121+// String returns the protocol representation of the message, without an ending
122+// "\r\n".
123 func (msg *Message) String() string {
124 var sb strings.Builder
125
126@@ -317,6 +334,7 @@ func (msg *Message) String() string {
127 return sb.String()
128 }
129
130+// IsValid reports whether the message is correctly formed.
131 func (msg *Message) IsValid() bool {
132 switch msg.Command {
133 case "AUTHENTICATE", "PING", "PONG":
134@@ -377,6 +395,7 @@ func (msg *Message) IsValid() bool {
135 }
136 }
137
138+// Time returns the time when the message has been sent, if present.
139 func (msg *Message) Time() (t time.Time, ok bool) {
140 var tag string
141 var year, month, day, hour, minute, second, millis int
142@@ -398,6 +417,8 @@ func (msg *Message) Time() (t time.Time, ok bool) {
143 return
144 }
145
146+// TimeOrNow returns the time when the message has been sent, or time.Now() if
147+// absent.
148 func (msg *Message) TimeOrNow() time.Time {
149 t, ok := msg.Time()
150 if ok {
151@@ -406,6 +427,7 @@ func (msg *Message) TimeOrNow() time.Time {
152 return time.Now().UTC()
153 }
154
155+// Severity is the severity of a server reply.
156 type Severity int
157
158 const (
159@@ -414,6 +436,7 @@ const (
160 SeverityFail
161 )
162
163+// ReplySeverity returns the severity of a server reply.
164 func ReplySeverity(reply string) Severity {
165 switch reply[0] {
166 case '4', '5':
167@@ -434,12 +457,15 @@ func ReplySeverity(reply string) Severity {
168 }
169 }
170
171+// Cap is a capability token in "CAP" server responses.
172 type Cap struct {
173 Name string
174 Value string
175 Enable bool
176 }
177
178+// ParseCaps parses the last argument (capability list) of "CAP LS/LIST/NEW/DEL"
179+// server responses.
180 func ParseCaps(caps string) (diff []Cap) {
181 for _, c := range strings.Split(caps, " ") {
182 if c == "" || c == "-" || c == "=" || c == "-=" {
183@@ -467,11 +493,14 @@ func ParseCaps(caps string) (diff []Cap) {
184 return
185 }
186
187+// Member is a token in RPL_NAMREPLY's last parameter.
188 type Member struct {
189 PowerLevel string
190 Name *Prefix
191 }
192
193+// ParseNameReply parses the last parameter of RPL_NAMREPLY, according to the
194+// membership prefixes of the server.
195 func ParseNameReply(trailing string, prefixes string) (names []Member) {
196 for _, word := range strings.Split(trailing, " ") {
197 if word == "" {