diff options
author | Hubert Hirtz <hubert@hirtz.pm> | 2020-11-21 12:29:51 +0100 |
---|---|---|
committer | Hubert Hirtz <hubert@hirtz.pm> | 2020-11-21 12:29:51 +0100 |
commit | c76171762b58bc90bc39838e8458f237ae916288 (patch) | |
tree | f5baf010a7f2b72b5ac4fb0c8d37102640856b48 /irc | |
parent | Only send the SelfJoinEvent when channel info is complete (diff) |
Document the IRC library
Diffstat (limited to 'irc')
-rw-r--r-- | irc/states.go | 72 | ||||
-rw-r--r-- | irc/tokens.go | 41 |
2 files changed, 86 insertions, 27 deletions
diff --git a/irc/states.go b/irc/states.go index 626f3b7..7722d20 100644 --- a/irc/states.go +++ b/irc/states.go @@ -42,6 +42,7 @@ func (auth *SASLPlain) Respond(challenge string) (res string, err error) { return } +// SupportedCapabilities is the set of capabilities supported by this library. var SupportedCapabilities = map[string]struct{}{ "account-notify": {}, "account-tag": {}, @@ -61,6 +62,8 @@ var SupportedCapabilities = map[string]struct{}{ "userhost-in-names": {}, } +// Values taken by the "@+typing=" client tag. TypingUnspec means the value or +// tag is absent. const ( TypingUnspec = iota TypingActive @@ -68,6 +71,13 @@ const ( TypingDone ) +// action contains the arguments of a user action. +// +// To keep connection reads and writes in a single coroutine, the library +// interface functions like Join("#channel") or PrivMsg("target", "message") +// don't interact with the IRC session directly. Instead, they push an action +// in the action channel. This action is then processed by the correct +// coroutine. type action interface{} type ( @@ -105,22 +115,25 @@ type ( } ) +// User is a known IRC user (we share a channel with it). type User struct { - Name *Prefix - AwayMsg string + Name *Prefix // the nick, user and hostname of the user if known. + AwayMsg string // the away message if the user is away, "" otherwise. } +// Channel is a joined channel. type Channel struct { - Name string - Members map[*User]string - Topic string - TopicWho *Prefix - TopicTime time.Time - Secret bool - - complete bool + Name string // the name of the channel. + Members map[*User]string // the set of members associated with their membership. + Topic string // the topic of the channel, or "" if absent. + TopicWho *Prefix // the name of the last user who set the topic. + TopicTime time.Time // the last time the topic has been changed. + Secret bool // whether the channel is on the server channel list. + + complete bool // whether this stucture is fully initialized. } +// SessionParams defines how to connect to an IRC server. type SessionParams struct { Nickname string Username string @@ -128,24 +141,25 @@ type SessionParams struct { Auth SASLClient - Debug bool + Debug bool // whether the Session should report all messages it sends and receive. } +// Session is an IRC session/connection/whatever. type Session struct { conn io.ReadWriteCloser - msgs chan Message - acts chan action - evts chan Event + msgs chan Message // incoming messages. + acts chan action // user actions. + evts chan Event // events sent to the user. debug bool running atomic.Value // bool registered bool - typings *Typings - typingStamps map[string]time.Time + typings *Typings // incoming typing notifications. + typingStamps map[string]time.Time // user typing instants. nick string - nickCf string + nickCf string // casemapped nickname. user string real string acct string @@ -154,13 +168,18 @@ type Session struct { availableCaps map[string]string enabledCaps map[string]struct{} - features map[string]string + features map[string]string // server ISUPPORT advertized features. - users map[string]*User - channels map[string]Channel - chBatches map[string]HistoryEvent + users map[string]*User // known users. + channels map[string]Channel // joined channels. + chBatches map[string]HistoryEvent // channel history batches being processed. } +// NewSession starts an IRC session from the given connection and session +// parameters. +// +// It returns an error when the paramaters are invalid, or when it cannot write +// to the connection. func NewSession(conn io.ReadWriteCloser, params SessionParams) (*Session, error) { s := &Session{ conn: conn, @@ -226,10 +245,12 @@ func NewSession(conn io.ReadWriteCloser, params SessionParams) (*Session, error) return s, nil } +// Running reports whether we are still connected to the server. func (s *Session) Running() bool { return s.running.Load().(bool) } +// Stop stops the session and closes the connection. func (s *Session) Stop() { if !s.Running() { return @@ -241,10 +262,13 @@ func (s *Session) Stop() { close(s.msgs) } +// Poll returns the event channel where incoming events are reported. func (s *Session) Poll() (events <-chan Event) { return s.evts } +// HasCapability reports whether the given capability has been negociated +// successfully. func (s *Session) HasCapability(capability string) bool { _, ok := s.enabledCaps[capability] return ok @@ -254,6 +278,7 @@ func (s *Session) Nick() string { return s.nick } +// NickCf is our casemapped nickname. func (s *Session) NickCf() string { return s.nickCf } @@ -274,6 +299,7 @@ func (s *Session) Casemap(name string) string { } } +// Users returns the list of all known nicknames. func (s *Session) Users() []string { users := make([]string, 0, len(s.users)) for _, u := range s.users { @@ -282,6 +308,8 @@ func (s *Session) Users() []string { return users } +// Names returns the list of users in the given channel, or nil if this channel +// is not known by the session. func (s *Session) Names(channel string) []Member { var names []Member if c, ok := s.channels[s.Casemap(channel)]; ok { @@ -296,6 +324,7 @@ func (s *Session) Names(channel string) []Member { return names } +// Typings returns the list of nickname who are currently typing. func (s *Session) Typings(target string) []string { targetCf := s.Casemap(target) var res []string @@ -333,6 +362,7 @@ func (s *Session) Topic(channel string) (topic string, who *Prefix, at time.Time return } +// SendRaw sends its given argument verbatim to the server. func (s *Session) SendRaw(raw string) { s.acts <- actionSendRaw{raw} } diff --git a/irc/tokens.go b/irc/tokens.go index f4ec3c7..86e6539 100644 --- a/irc/tokens.go +++ b/irc/tokens.go @@ -8,6 +8,8 @@ import ( "time" ) +// CasemapASCII of name is the canonical representation of name according to the +// ascii casemapping. func CasemapASCII(name string) string { var sb strings.Builder sb.Grow(len(name)) @@ -20,6 +22,8 @@ func CasemapASCII(name string) string { return sb.String() } +// CasemapASCII of name is the canonical representation of name according to the +// rfc-1459 casemapping. func CasemapRFC1459(name string) string { var sb strings.Builder sb.Grow(len(name)) @@ -40,20 +44,21 @@ func CasemapRFC1459(name string) string { return sb.String() } -func word(s string) (w, rest string) { +// word returns the first word of s and the rest of s. +func word(s string) (word, rest string) { split := strings.SplitN(s, " ", 2) - if len(split) < 2 { - w = split[0] + word = split[0] rest = "" } else { - w = split[0] + word = split[0] rest = split[1] } - return } +// tagEscape returns the the value of '\c' given c according to the message-tags +// specification. func tagEscape(c rune) (escape rune) { switch c { case ':': @@ -67,10 +72,11 @@ func tagEscape(c rune) (escape rune) { default: escape = c } - return } +// unescapeTagValue removes escapes from the given string and replaces them with +// their meaningful values. func unescapeTagValue(escaped string) string { var builder strings.Builder builder.Grow(len(escaped)) @@ -96,6 +102,7 @@ func unescapeTagValue(escaped string) string { return builder.String() } +// escapeTagValue does the inverse operation of unescapeTagValue. func escapeTagValue(unescaped string) string { var sb strings.Builder sb.Grow(len(unescaped) * 2) @@ -163,6 +170,8 @@ type Prefix struct { Host string } +// ParsePrefix parses a "nick!user@host" combination (or a prefix) from the given +// string. func ParsePrefix(s string) (p *Prefix) { if s == "" { return @@ -185,6 +194,7 @@ func ParsePrefix(s string) (p *Prefix) { return } +// Copy makes a copy of the prefix, but doesn't copy the internal strings. func (p *Prefix) Copy() *Prefix { if p == nil { return nil @@ -194,6 +204,7 @@ func (p *Prefix) Copy() *Prefix { return res } +// String returns the "nick!user@host" representation of the prefix. func (p *Prefix) String() string { if p == nil { return "" @@ -210,6 +221,7 @@ func (p *Prefix) String() string { } } +// Message is the representation of an IRC message. type Message struct { Tags map[string]string Prefix *Prefix @@ -217,6 +229,8 @@ type Message struct { Params []string } +// ParseMessage parses the message from the given string, which must be trimmed +// of "\r\n" beforehand. func ParseMessage(line string) (msg Message, err error) { line = strings.TrimLeft(line, " ") if line == "" { @@ -268,6 +282,7 @@ func ParseMessage(line string) (msg Message, err error) { return } +// IsReply reports whether the message command is a server reply. func (msg *Message) IsReply() bool { if len(msg.Command) != 3 { return false @@ -280,6 +295,8 @@ func (msg *Message) IsReply() bool { return true } +// String returns the protocol representation of the message, without an ending +// "\r\n". func (msg *Message) String() string { var sb strings.Builder @@ -317,6 +334,7 @@ func (msg *Message) String() string { return sb.String() } +// IsValid reports whether the message is correctly formed. func (msg *Message) IsValid() bool { switch msg.Command { case "AUTHENTICATE", "PING", "PONG": @@ -377,6 +395,7 @@ func (msg *Message) IsValid() bool { } } +// Time returns the time when the message has been sent, if present. func (msg *Message) Time() (t time.Time, ok bool) { var tag string var year, month, day, hour, minute, second, millis int @@ -398,6 +417,8 @@ func (msg *Message) Time() (t time.Time, ok bool) { return } +// TimeOrNow returns the time when the message has been sent, or time.Now() if +// absent. func (msg *Message) TimeOrNow() time.Time { t, ok := msg.Time() if ok { @@ -406,6 +427,7 @@ func (msg *Message) TimeOrNow() time.Time { return time.Now().UTC() } +// Severity is the severity of a server reply. type Severity int const ( @@ -414,6 +436,7 @@ const ( SeverityFail ) +// ReplySeverity returns the severity of a server reply. func ReplySeverity(reply string) Severity { switch reply[0] { case '4', '5': @@ -434,12 +457,15 @@ func ReplySeverity(reply string) Severity { } } +// Cap is a capability token in "CAP" server responses. type Cap struct { Name string Value string Enable bool } +// ParseCaps parses the last argument (capability list) of "CAP LS/LIST/NEW/DEL" +// server responses. func ParseCaps(caps string) (diff []Cap) { for _, c := range strings.Split(caps, " ") { if c == "" || c == "-" || c == "=" || c == "-=" { @@ -467,11 +493,14 @@ func ParseCaps(caps string) (diff []Cap) { return } +// Member is a token in RPL_NAMREPLY's last parameter. type Member struct { PowerLevel string Name *Prefix } +// ParseNameReply parses the last parameter of RPL_NAMREPLY, according to the +// membership prefixes of the server. func ParseNameReply(trailing string, prefixes string) (names []Member) { for _, word := range strings.Split(trailing, " ") { if word == "" { |