summaryrefslogtreecommitdiff
path: root/irc
diff options
context:
space:
mode:
authorHubert Hirtz <hubert@hirtz.pm>2020-11-21 12:29:51 +0100
committerHubert Hirtz <hubert@hirtz.pm>2020-11-21 12:29:51 +0100
commitc76171762b58bc90bc39838e8458f237ae916288 (patch)
treef5baf010a7f2b72b5ac4fb0c8d37102640856b48 /irc
parentOnly send the SelfJoinEvent when channel info is complete (diff)
Document the IRC library
Diffstat (limited to 'irc')
-rw-r--r--irc/states.go72
-rw-r--r--irc/tokens.go41
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 == "" {