irc

package module
v0.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 22, 2022 License: MIT Imports: 14 Imported by: 0

README

This is a work in progress. The API is highly unstable.

IRC

Package irc provides an IRC client implementation for Go (golang).

Features

  • Extensive documentation
  • A small, but powerful API
  • Handler packages for common functionality
  • Useful out of the box

Usage Example: Hello, #World

The following code connects to an IRC server, waits for RPL_WELCOME, then requests to join a channel called #world, waits for the server to tell us that we've joined, then sends the message "Hello!" to #world, then disconnects with the message "Goodbye.".

package main

import (
  "context"
  "log"

  "github.com/Travis-Britz/irc"
)

func main() {
  bot := &irc.Client{
    Addr: "irc.example.com:6697",
    Nickname: "HelloBot",
  }

  handler := &irc.Router{}
  handler.OnConnect(func(w irc.MessageWriter, m *irc.Message) {
    w.WriteMessage(irc.Join("#world"))
  })
  handler.OnJoin(func(w irc.MessageWriter, m *irc.Message) {
    w.WriteMessage(irc.Msg("#world", "Hello!"))
    w.WriteMessage(irc.Quit("Goodbye."))
  }).MatchChan("#world").MatchClient(bot)

  err := bot.ConnectAndRun(context.Background(), handler)
  if err != nil {
    log.Println(err)
  }
}

More detailed examples are available in the examples section of the godoc.

Docs TODO

These are quick descriptions for concepts which need to be written and expanded with more detail.

  • Extensions which add support for capabilities should listen for the CAP LS event to write their CAP REQ, and should listen for CAP ACK/NAK/LIST before enabling themselves. LS and LIST will always have the list of capabilities in the final parameter.
  • Extensions should attach themselves as middleware, and almost always call the next handler. Failing to do so may break the client.
  • Route matchers, handler ordering rules
Decisions TODO

Things to decide that will be hard to change later without breaking code.

  • Should the router stop on the first matching route or should it run every matching route? Running all would be less complicated for users to figure out and less of a headache when commands aren't working. It would also force them to be more careful to only let one route match.
  • Should the client handle all capability negotiation (and extensions register their ability to handle caps with a callback) or should the client just pass caps on and expect each extension to do its own capability message parsing? Doing everything with extension middleware is easier to code for me, but perhaps less reliable in terms of quality if every extension author is expected to understand capability negotiation as well as their specific extension's role.

FAQ

  1. Can I use this in Production? Yes
  2. What is NAQ? Never Asked Questions

NAQ

  1. Should I use this in Production? No, lmao

Documentation

Overview

Package irc provides an IRC client implementation.

This overview provides brief introductions for types and concepts. The godoc for each type contains expanded documentation.

Jump to the package examples to see what writing client code looks like with this package.

The README file links to available extension packages for features such as state tracking, flood protection, and more.

API

These are the main interfaces and structs that you will interact with while using this package:

// A Handler responds to an IRC message.
type Handler interface {
	SpeakIRC(MessageWriter, *Message)
}

// A MessageWriter can write an IRC message.
type MessageWriter interface {
	WriteMessage(encoding.TextMarshaler)
}

// Message represents any incoming our outgoing IRC line.
type Message struct {

	// Tags contains any IRCv3 message tags.
	Tags    Tags

	// Source is where the message originated from.
	Source  Prefix

	// Command is the IRC verb or numeric (event type) such as PRIVMSG, NOTICE, 001, etc.
	Command Command

	// Params contains all the message parameters.
	Params  Params
}

// A Client manages a connection to an IRC server.
type Client struct {
	//...
}

// ConnectAndRun starts the client.
func (c *Client) ConnectAndRun(ctx context.Context, h Handler) error {
	//...
}

Client

The Client type provides a simple abstraction around an IRC connection. It manages reading and writing messages to the IRC connection and calls your handler for each message it parses. It also deals with protocol concerns like ping timeouts.

Handler

This interface enables the development of handler packages.

type Handler interface {
  SpeakIRC(MessageWriter, *Message)
}

Such packages may implement protocols such as IRCv3 Message IDs, or common bot concerns like preferred/alternate nickname for disconnects, flood protection, and channel state.

Because the Handler interface for the irc package mimics the signature of the http.Handler interface, most patterns for http middleware can also be applied to irc handlers. Search results for phrases like "golang http middleware pattern", "adapter patterns", and "decorator patterns" should all return concepts from tutorials and blog posts that can be applied to this interface.

MessageWriter

The MessageWriter interface accepts any type that knows how to marshal itself into a line of IRC-encoded text.

Most of the time it makes sense to send a Message struct, either by using the NewMessage function or any of the related constructors such as irc.Msg, irc.Notice, irc.Describe, etc.

However, it can also be very simple to implement yourself:

// w is an irc.MessageWriter
w.WriteMessage(rawLine("PRIVMSG #World :Hello!"))

type rawLine string
// MarshalText implements encoding.TextMarshaler
func (l rawLine) MarshalText() ([]byte,error) {
	return []byte(l),nil
}

The named Message constructors (irc.Msg, irc.Notice, etc.) should generally be preferred because they explicitly list the available parameters for each command. This provides type safety, ordering safety, and most IDEs will provide intellisense suggestions and documentation for each parameter.

In other words:

// prefer this:
w.WriteMessage(irc.Msg("#world", "Hello!"))
// instead of this:
w.WriteMessage(irc.NewMessage(irc.CmdPrivmsg, "#world", "Hello!"))
// and definitely instead of this:
w.WriteMessage(rawLine(...))

Router

The Router type is an implementation of Handler. It provides a convenient way to route incoming messages to specific handler functions by matching against message attributes like the command, source, target channel, and much more. It also provides an easy way to apply middleware, either globally or to specific routes. You are not required to use it, however. You can just as easily write your own message handler.

It performs a role comparable to http.ServeMux, though it is not really a multiplexer.

r := &irc.Router{}
r.OnText("!watchtime*", handleCommandWatchtime)
//...
func handleCommandWatchtime(...) {
	//...
}

Middleware

Middleware are just handlers. The term "middleware" applies to handlers which follow a pattern of accepting a handler as one of their arguments and returning a handler.

// logHandler is a function that follows the middleware pattern and implements the Handler interface.
func logHandler(next irc.Handler) irc.HandlerFunc {
	return func(w irc.MessageWriter, m *irc.Message) {
		log.Printf("parsed: %#v\n", m)
		next.SpeakIRC(w, m)
	}
}

func main() {
	//...
	handler := &irc.Router{}
	//...

	err := client.ConnectAndRun(..., logHandler(handler))
}

Middleware can intercept outgoing messages by decorating the MessageWriter, as well as call the next handler with a modified *Message. These two abilities allow well-written packages to provide middleware that extend a client with nearly any IRC capability.

Because the ordering of received messages is important for calculating various client states, it is generally not safe for middleware handlers to operate concurrently unless they can maintain message ordering.

Request Lifecycle

To bring it all together, this is the general sequence of events when running a client:

  • A Client's ConnectAndRun method is called and given a Handler.
  • Internally, the client wraps the provided handler with additional middleware handlers that implement core IRC features.
  • ConnectAndRun calls DialFn to connect to an IRC stream.
  • The client will begin reading lines from the stream and parse them into Message structs until the connection is closed.

Each Message parsed from the stream will result in a call to the client's handler, which is given a MessageWriter and reference to the parsed Message struct. Assuming that you use the package's Router type as your handler, this is what that sequence looks like:

  • The internal client middleware that wrapped your handler will execute on order, calling next.SpeakIRC until they reach the Router (your handler).
  • The global middleware attached to the Router, if any, will execute in order.
  • The Router will test each route until it finds one where all conditions match.
  • If the matched route has any middleware, they will execute in order.
  • Finally, the handler provided for the route will execute.

Any of these actions could occur at any point in the chain:

  • A handler decides to write a message.
  • A handler halts execution by returning without calling the next handler.
  • A handler interprets a message to update its own internal state.
  • A handler calls the next handler with a modified version of the message.
  • A handler calls the next handler with a new MessageWriter that is decorated with a additional functions.

Message Formatting

This package does not implement message formatting. That is to say, there are no irc.Msgf or related functions. Formatting requirements vary widely by application. Some applications will want to extend the formatting rules with their own replacement sequences to include IRC color formatting in replies. Rather than implement nonstandard rules here (and force users to look up replacements), the canonical way to write formatted replies in the style of fmt.Printf is to write your own reply helper functions. For example:

func replyTof(w irc.MessageWriter, m *irc.Message, format string, args ...interface{}) {
	target,_ := m.Chan()
	reply := irc.Msg(target, fmt.Sprintf(format, ...args))
	w.WriteMessage(reply)
}
Example

Hello, #World: The following code connects to an IRC server, waits for RPL_WELCOME, then requests to join a channel called #world, waits for the server to tell us that we've joined, then sends the message "Hello!" to #world, then disconnects with the message "Goodbye.".

package main

import (
	"context"
	"log"

	"github.com/Travis-Britz/irc"
)

func main() {
	bot := &irc.Client{
		Addr:     "irc.example.com:6697",
		Nickname: "HelloBot",
	}
	r := &irc.Router{}
	r.OnConnect(func(w irc.MessageWriter, m *irc.Message) {
		w.WriteMessage(irc.Join("#world"))
	})
	r.OnJoin(func(w irc.MessageWriter, m *irc.Message) {
		w.WriteMessage(irc.Msg("#world", "Hello!"))
		w.WriteMessage(irc.Quit("Goodbye."))
	}).MatchChan("#world").MatchClient(bot)

	// run the bot (blocking until exit)
	err := bot.ConnectAndRun(context.Background(), r)
	if err != nil {
		log.Println(err)
	}
}
Output:

Example (Router)

This example uses the message router to perform more complicated message matching with an event callback style. Connects to an IRC server, joins a channel called "#world", sends the message "Hello!", then quits when CTRL+C is pressed.

package main

import (
	"context"
	"log"
	"os"
	"os/signal"
	"strings"

	"github.com/Travis-Britz/irc"
)

func main() {
	ctx, cancel := context.WithCancel(context.Background())

	bot := &irc.Client{
		Addr:     "irc.swiftirc.net:6697",
		Nickname: "HelloBot",
	}

	// Router implements irc.Handler and maps incoming messages (events) to a handler.
	r := &irc.Router{}

	r.OnConnect(func(w irc.MessageWriter, m *irc.Message) {
		w.WriteMessage(irc.Join("#world"))
	})

	// r.OnKick(func(w irc.MessageWriter, m *irc.Message) {
	// 	kicked, _ := m.Affected()
	// 	if !kicked.Is(bot.Nick().String()) {
	// 		return
	// 	}
	//
	// 	w.WriteMessage(irc.Msg(e.Nick().String(), "You kicked me!"))
	// })

	r.OnJoin(func(w irc.MessageWriter, m *irc.Message) {
		w.WriteMessage(irc.Msg("#world", "Hello!"))
	}).
		MatchChan("#world").
		MatchClient(bot)

	// When somebody types "!greet nickname" we respond with "Hello, nickname!".
	r.OnText("!greet &", func(w irc.MessageWriter, m *irc.Message) {
		text, _ := m.Text()
		fields := strings.Fields(text)
		channelName, _ := m.Chan()
		reply := "Hello, " + fields[1] + "!" // the second field is guaranteed to exist due to the wildcard format
		w.WriteMessage(irc.Msg(channelName, reply))
	})

	// Listen for interrupt signals (Ctrl+C) and initiate
	// a graceful shutdown sequence when one is received.
	shutdown := make(chan os.Signal)
	go func() {
		<-shutdown
		cancel()
	}()
	signal.Notify(shutdown, os.Interrupt)

	// run the bot (blocking until exit)
	err := bot.ConnectAndRun(ctx, r)
	if err != nil {
		log.Println(err)
	}
}
Output:

Example (Simple)

The simplest possible implementation of a Message handler. In this case, "simple" means it is not using package features. The code should be considered to be a "messy" implementation, but demonstrates how easy it is to get down to the protocol level, if desired.

package main

import (
	"context"
	"fmt"
	"log"
	"strings"

	"github.com/Travis-Britz/irc"
)

const myName = "HelloBot"

// myHandler is an irc.HandlerFunc.
//
// On connection success (001), it joins #MyChannel.
//
// On join events, it checks if the joining nickname matched myName and the channel matched #MyChannel
// before sending an introduction.
//
// On privmsg events check if the message target matched our name (indicating a query/DM) and the first
// word begins with "Hello" before responding with "hey there!".
func myHandler(w irc.MessageWriter, m *irc.Message) {
	switch m.Command {
	case "001":
		w.WriteMessage(rawLine("JOIN #MyChannel"))
	case "JOIN":
		if !m.Source.Nick.Is(myName) {
			return
		}
		if !strings.EqualFold("#MyChannel", m.Params.Get(1)) {
			return
		}

		w.WriteMessage(rawLine("PRIVMSG #MyChannel :Hello everybody, my name is " + myName))
	case "PRIVMSG":
		if m.Params.Get(1) == myName {
			if msgBody := m.Params.Get(2); strings.HasPrefix(msgBody, "Hello") {
				w.WriteMessage(rawLine(fmt.Sprintf("PRIVMSG %s :hey there!", m.Source.Nick)))
			}
		}
	}
}

// rawLine is an IRC-formatted message.
type rawLine string

// MarshalText implements encoding.TextMarshaler, which
// is used by irc.MessageWriter.
func (l rawLine) MarshalText() ([]byte, error) {
	return []byte(l), nil
}

// The simplest possible implementation of a Message handler.
// In this case, "simple" means it is not using package features. The code should be
// considered to be a "messy" implementation, but demonstrates how easy it is to get
// down to the protocol level, if desired.
func main() {
	bot := &irc.Client{
		Addr:     "irc.example.com:6697",
		Nickname: myName,
	}

	// we need to explicitly convert myHandler to the irc.HandlerFunc type
	err := bot.ConnectAndRun(context.Background(), irc.HandlerFunc(myHandler))
	if err != nil {
		log.Fatal(err)
	}

}
Output:

Index

Examples

Constants

View Source
const (
	CmdAdmin    = "ADMIN"    // Get information about the administrator of a server.
	CmdAway     = "AWAY"     // Set an automatic reply string for any PRIVMSG commands.
	CmdCap      = "CAP"      // IRCv3 Capability negotiation.
	CmdConnect  = "CONNECT"  // Request a new connection to another server immediately.
	CmdDie      = "DIE"      // Shutdown the server.
	CmdError    = "ERROR"    // Report a serious or fatal error to a peer.
	CmdInfo     = "INFO"     // Get information describing a server.
	CmdInvite   = "INVITE"   // Invite a user to a channel.
	CmdIsOn     = "ISON"     // Determine if a nickname is currently on IRC.
	CmdJoin     = "JOIN"     // Join a channel.
	CmdKick     = "KICK"     // Request the forced removal of a user from a channel.
	CmdKill     = "KILL"     // Close a client-server connection by the server which has the actual connection.
	CmdLinks    = "LINKS"    // List all servernames which are known by the server answering the query.
	CmdList     = "LIST"     // List channels and their topics.
	CmdLUsers   = "LUSERS"   // Get statistics about the size of the IRC network.
	CmdMode     = "MODE"     // User mode.
	CmdMOTD     = "MOTD"     // Get the Message of the Day.
	CmdNames    = "NAMES"    // List all visible nicknames.
	CmdNick     = "NICK"     // ":<newnick>" Define a nickname.
	CmdNJoin    = "NJOIN"    // Exchange the list of channel members for each channel between servers.
	CmdNotice   = "NOTICE"   // Send a notice message to specific users or channels.
	CmdOper     = "OPER"     // Obtain operator privileges.
	CmdPart     = "PART"     // Leave a channel.
	CmdPass     = "PASS"     // Set a connection password.
	CmdPing     = "PING"     // Test for the presence of an active client or server.
	CmdPong     = "PONG"     // Reply to a PING message.
	CmdPrivmsg  = "PRIVMSG"  // Send private messages between users, as well as to send messages to channels.
	CmdQuit     = "QUIT"     // Terminate the client session.
	CmdRehash   = "REHASH"   // Force the server to re-read and process its configuration file.
	CmdRestart  = "RESTART"  // Force the server to restart itself.
	CmdServer   = "SERVER"   // Register a new server.
	CmdService  = "SERVICE"  // Register a new service.
	CmdServList = "SERVLIST" // List services currently connected to the network.
	CmdSQuery   = "SQUERY"   //
	CmdSQuit    = "SQUIT"    // Break a local or remote server link.
	CmdStats    = "STATS"    // Get server statistics.
	CmdTagMsg   = "TAGMSG"   // https://ircv3.net/specs/extensions/message-tags.html
	CmdTime     = "TIME"     // Get the local time from the specified server.
	CmdTopic    = "TOPIC"    // Change or view the topic of a channel.
	CmdTrace    = "TRACE"    // Find the route to a server and information about it's peers.
	CmdUser     = "USER"     // Specify the username, hostname and realname of a new user.
	CmdUserHost = "USERHOST" // Get a list of information about upto 5 nicknames.
	CmdUsers    = "USERS"    // Get a list of users logged into the server.
	CmdVersion  = "VERSION"  // Get the version of the server program.
	CmdWAllOps  = "WALLOPS"  // Send a message to all currently connected users who have set the 'w' user mode.
	CmdWho      = "WHO"      // List a set of users.
	CmdWhoIs    = "WHOIS"    // Get information about a specific user.
	CmdWhoWas   = "WHOWAS"   // Get information about a nickname which no longer exists.
)

irc commands which may be sent or received by a client.

View Source
const (
	RplWelcome  = "001" // "Welcome to the Internet Relay Network <nick>!<user>@<host>"
	RplYourHost = "002" // "Your host is <servername>, running version <ver>"
	RplCreated  = "003" // "This server was created <date>"
	RplMyInfo   = "004" // "<servername> <version> <available user modes> <available channel modes>"
	RplISupport = "005" // http://www.irc.org/tech_docs/005.html http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt https://www.mirc.com/isupport.html
	RplBounce   = "010" // "Try server <server name>, port <port number>" - https://modern.ircdocs.horse/#rplbounce-010
)

irc connection reply codes.

View Source
const (
	RplTraceLink       = "200" // "Link <version & debug level> <destination><next server> V<protocol version> <link uptime in seconds><backstream sendq> <upstream sendq>"
	RplTraceConnecting = "201" // "Try. <class> <server>"
	RplTraceHandshake  = "202" // "H.S. <class> <server>"
	RplTraceUnknown    = "203" // "???? <class> [<client IP address in dot form>]"
	RplTraceOperator   = "204" // "Oper <class> <nick>"
	RplTraceUser       = "205" // "User <class> <nick>"
	RplTraceServer     = "206" // "Serv <class> <int>S <int>C <server><nick!user|*!*>@<host|server> V<protocol version>"
	RplTraceService    = "207" // "Service <class> <name> <type> <activetype>"
	RplTraceNewtype    = "208" // "<newtype> 0 <client name>"
	RplTraceClass      = "209" // "Class <class> <count>"
	RplTraceReconnect  = "210" // Unused.
	RplStatsLinkInfo   = "211" // "<linkname> <sendq> <sent messages> <sentKbytes> <received messages> <received Kbytes> <timeopen>"
	RplStatsCommands   = "212" // "<command> <count> <byte count> <remotecount>"
	RplEndOfStats      = "219" // "<stats letter> :End of STATS report"
	RplUModeIs         = "221" // "<user mode string>"
	RplServList        = "234" // "<name> <server> <mask> <type><hopcount> <info>"
	RplServListEnd     = "235" // "<mask> <type> :End of service listing"
	RplStatsUptime     = "242" // ":Server Up %d days %d:%02d:%02d"
	RplStatsOLine      = "243" // "O <hostmask> * <name>"
	RplLUserClient     = "251" // ":There are <integer> users and <integer> services on<integer> servers"
	RplLUserOp         = "252" // "<integer> :operator(s) online"
	RplLUserUknownL    = "253" // "<integer> :unknown connection(s)"
	RplLUserChannels   = "254" // "<integer> :channels formed"
	RplLUserMe         = "255" // ":I have <integer> clients and <integer> servers"
	RplAdminMe         = "256" // "<server> :Administrative info"
	RplAdminLoc1       = "257" // ":<admin info>"
	RplAdminLoc2       = "258" // ":<admin info>"
	RplAdminEmail      = "259" // ":<admin info>"
	RplTraceLog        = "261" // "File <logfile> <debug level>"
	RplTraceEnd        = "262" // "<server name> <version & debug level> :End of TRACE"
	RplTryAgain        = "263" // "<command> :Please wait a while and try again."
	RplAway            = "301" // "<nick> :<away message>"
	RplUserHost        = "302" // ":*1<reply> *( " " <reply> )"
	RplIsOn            = "303" // ":*1<nick> *( " " <nick> )"
	RplUnAway          = "305" // ":You are no longer marked as being away"
	RplNowAway         = "306" // ":You have been marked as being away"
	RplWhoIsUser       = "311" // "<nick> <user> <host> * :<real name>"
	RplWhoIsServer     = "312" // "<nick> <server> :<server info>"
	RplWhoIsOperator   = "313" // "<nick> :is an IRC operator"
	RplWhoWasUser      = "314" // "<nick> <user> <host> * :<real name>"
	RplEndOfWho        = "315" // "<name> :End of WHO list"
	RplWhoIsIdle       = "317" // "<nick> <integer> :seconds idle"
	RplEndOfWhoIs      = "318" // "<nick> :End of WHOIS list"
	RplWhoIsChannels   = "319" // "<nick> :*( ( "@" / "+" ) <channel>" " )"
	RplListStart       = "321" // Obsolete.
	RplList            = "322" // "<channel> <# visible> :<topic>"
	RplListEnd         = "323" // ":End of LIST"
	RplChannelModeIs   = "324" // "<channel> <mode> <mode params>"
	RplUniqOpIs        = "325" // "<channel> <nickname>"
	RplNoTopic         = "331" // "<channel> :No topic is set"
	RplTopic           = "332" // "<channel> :<topic>"
	RplWhoisBot        = "335" // "<nick> <target> :<message>"
	RplInviting        = "341" // "<channel> <nick>"
	RplSummoning       = "342" // "<user> :Summoning user to IRC"
	RplInviteList      = "346" // "<channel> <invitemask>"
	RplEndOfInviteList = "347" // "<channel> :End of channel invite list"
	RplExceptList      = "348" // "<channel> <exceptionmask>"
	RplEndOfExceptList = "349" // "<channel> :End of channel exception list"
	RplVersion         = "351" // "<version>.<debuglevel> <server>:<comments>"
	RplWhoReply        = "352" // "<channel> <user> <host> <server><nick> ( "H" / "G" > ["*"] [ ("@" / "+" ) ] :<hopcount> <real name>"
	RplNamReply        = "353" // "( "=" / "*" / "@" ) <channel>:[ "@" / "+" ] <nick> *( " " ["@" / "+" ] <nick> )"
	RplLinks           = "364" // "<mask> <server> :<hopcount> <serverinfo>"
	RplEndOfLinks      = "365" // "<mask> :End of LINKS list"
	RplEndOfNames      = "366" // "<channel> :End of NAMES list"
	RplBanList         = "367" // "<channel> <banmask>"
	RplEndOfBanList    = "368" // "<channel> :End of channel ban list"
	RplEndOfWhoWas     = "369" // "<nick> :End of WHOWAS"
	RplInfo            = "371" // ":<string>"
	RplMOTD            = "372" // ":- <text>"
	RplEndOfInfo       = "374" // ":End of INFO list"
	RplMOTDStart       = "375" // ":- <server> Message of the day - "
	RplEndOfMOTD       = "376" // ":End of MOTD command"
	RplYoureOper       = "381" // ":You are now an IRC operator"
	RplRehashing       = "382" // "<config file> :Rehashing"
	RplYoureService    = "383" // "You are service <servicename>"
	RplTime            = "391" // "<server> :<string showing server's local time>"
	RplUsersStart      = "392" // ":UserID Terminal Host"
	RplUsers           = "393" // ":<username> <ttyline> <hostname>"
	RplEndOfUsers      = "394" // ":End of users"
	RplNoUsers         = "395" // ":Nobody logged in"
	RplHostHidden      = "396" // "fubarbot <host> :is now your displayed host" Reply to a user when user mode +x (host masking) was set successfully https://www.alien.net.au/irc/irc2numerics.html
)

irc command reply codes.

View Source
const (
	RplErrNoSuchNick        = "401" // "<nickname> :No such nick/channel"
	RplErrNoSuchServer      = "402" // "<server name> :No such server"
	RplErrNoSuchChannel     = "403" // "<channel name> :No such channel"
	RplErrCannotSendToChan  = "404" // "<channel name> :Cannot send to channel"
	RplErrTooManyChannels   = "405" // "<channel name> :You have joined too many channels"
	RplErrWasNoSuchNick     = "406" // "<nickname> :There was no such nickname"
	RplErrTooManyTargets    = "407" // "<target> :<error code> recipients. <abortmessage>"
	RplErrNoSuchService     = "408" // "<service name> :No such service"
	RplErrNoOrigin          = "409" // ":No origin specified"
	RplErrInvalidCapCmd     = "410"
	RplErrNoRecipient       = "411" // ":No recipient given (<command>)"
	RplErrNoTextToSend      = "412" // ":No text to send"
	RplErrNoToplevel        = "413" // "<mask> :No toplevel domain specified"
	RplErrWildToplevel      = "414" // "<mask> :Wildcard in toplevel domain"
	RplErrBadMask           = "415" // "<mask> :Bad Server/host mask"
	RplErrUnknownCommand    = "421" // "<command> :Unknown command"
	RplErrNoMOTD            = "422" // ":MOTD File is missing"
	RplErrNoAdminInfo       = "423" // "<server> :No administrative info available"
	RplErrFileError         = "424" // ":File error doing <file op> on <file>"
	RplErrNoNicknameGiven   = "431" // ":No nickname given"
	RplErrErroneousNickname = "432" // "<client> <nick> :Erroneus nickname"
	RplErrNicknameInUse     = "433" // "<client> <nick> :Nickname is already in use"
	RplErrNickCollision     = "436" // "<nick> :Nickname collision KILL from<user>@<host>"
	RplErrUnavailResource   = "437" // "<nick/channel> :Nick/channel is temporarily unavailable"
	RplErrUserNotInChannel  = "441" // "<nick> <channel> :They aren't on that channel"
	RplErrNotOnChannel      = "442" // "<channel> :You're not on that channel"
	RplErrUserOnChannel     = "443" // "<user> <channel> :is already on channel"
	RplErrNoLogin           = "444" // "<user> :User not logged in"
	RplErrSummonDisabled    = "445" // ":SUMMON has been disabled"
	RplErrUsersDisabled     = "446" // ":USERS has been disabled"
	RplErrNotRegistered     = "451" // ":You have not registered"
	RplErrNeedMoreParams    = "461" // "<command> :Not enough parameters"
	RplErrAlreadyRegistered = "462" // ":Unauthorized command (already registered)"
	RplErrNoPermForHost     = "463" // ":Your host isn't among the privileged"
	RplErrPasswdMismatch    = "464" // ":Password incorrect"
	RplErrYoureBannedCreep  = "465" // ":You are banned from this server"
	RplErrYouWillBeBanned   = "466" //
	RplErrKeySet            = "467" // "<channel> :Channel key already set"
	RplErrChannelIsFull     = "471" // "<channel> :Cannot join channel (+l)"
	RplErrUnknownMode       = "472" // "<char> :is unknown mode char to me for <channel>"
	RplErrInviteOnlyChan    = "473" // "<channel> :Cannot join channel (+i)"
	RplErrBannedFromChan    = "474" // "<channel> :Cannot join channel (+b)"
	RplErrBadChannelKey     = "475" // "<channel> :Cannot join channel (+k)"
	RplErrBadChanMask       = "476" // "<channel> :Bad Channel Mask"
	RplErrNoChanModes       = "477" // "<channel> :Channel doesn't support modes"
	RplErrBanListFull       = "478" // "<channel> <char> :Channel list is full"
	RplErrNoPrivileges      = "481" // ":Permission Denied- You're not an IRC operator"
	RplErrChanOPrivsNeeded  = "482" // "<channel> :You're not channel operator"
	RplErrCantKillServer    = "483" // ":You can't kill a server!"
	RplErrRestricted        = "484" // ":Your connection is restricted!"
	RplErrUniqOPrivsNeeded  = "485" // ":You're not the original channel operator"
	RplErrNoOperHost        = "491" // ":No O-lines for your host"
	RplErrUModeUnknownFlag  = "501" // ":Unknown MODE flag"
	RplErrUsersDontMatch    = "502" // ":Cannot change mode for other users"
)

irc error reply codes.

View Source
const (
	CTCPAction = "_CTCP_QUERY_ACTION"

	CTCPVersionQuery    = "_CTCP_QUERY_VERSION"
	CTCPVersionReply    = "_CTCP_REPLY_ACTION"
	CTCPClientInfoQuery = "_CTCP_QUERY_CLIENTINFO"
	CTCPClientInfoReply = "_CTCP_REPLY_CLIENTINFO"

	CTCPPingQuery = "_CTCP_QUERY_PING"
	CTCPPingReply = "_CTCP_REPLY_PING"
	CTCPTimeQuery = "_CTCP_QUERY_TIME"
	CTCPTimeReply = "_CTCP_REPLY_TIME"
)

Client-to-Client Protocol command constants. These commands are NOT sent by the server; they are instead generated internally as replacements for CTCP-formatted PRIVMSG and NOTICE messages.

For convenience, these constants are defined for well-known CTCP messages. To create handlers that match arbitrary CTCP commands and replies, see NewCTCPCmd and NewCTCPReplyCmd.

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

type Client struct {

	// The address ("host:port") of the IRC server. Only TLS connections are supported; use DialFn for anything else.
	// Addr is only used when DialFn is nil.
	Addr string

	// The nickname used by the Client when connecting to an IRC network (required).
	// Nicknames cannot contain spaces.
	Nickname string

	// The user name (required).
	// User cannot contain spaces.
	User string

	// The realname of the client (required).
	// Also referred to as the gecos field.
	// Realname may contain spaces
	Realname string

	// The connection password (optional: depends on the network).
	Pass string

	// DialFn is a function that accepts no parameters and returns an io.ReadWriteCloser and error.
	//
	// The returned connection can be any io.ReadWriteCloser: irc, ircs, ws, wss, a server mock, etc.
	// The only requirement is that the stream consists of CRLF-delimited IRC messages.
	//
	// When DialFn is nil, the default behavior dials Addr with tls.Dial.
	DialFn func() (io.ReadWriteCloser, error)

	// ErrorLog specifies an optional logger for errors returned from parsing and encoding messages.
	// If nil, logging is done via the log package's standard logger.
	ErrorLog *log.Logger
	// contains filtered or unexported fields
}

A Client manages a connection to an IRC server. It reads/writes IRC lines on the connection, and calls the handler for each Message it parses from the connection.

Example (DialFn)
package main

import (
	"io"
	"net"

	"github.com/Travis-Britz/irc"
)

func main() {
	client := &irc.Client{Nickname: "WiZ"}
	client.DialFn = func() (io.ReadWriteCloser, error) {
		return net.Dial("tcp", "irc.example.com:6667")
	}
}
Output:

Example (DialFnDecorated)
package main

import (
	"io"
	"net"
	"os"

	"github.com/Travis-Britz/irc"
	"github.com/Travis-Britz/irc/ircdebug"
)

func main() {
	client := &irc.Client{Nickname: "WiZ"}
	client.DialFn = func() (io.ReadWriteCloser, error) {
		conn, err := net.Dial("tcp", "irc.example.com:6667")
		return ircdebug.WriteTo(os.Stdout, conn, "-> ", "<- "), err
	}
}
Output:

func (*Client) ConnectAndRun

func (c *Client) ConnectAndRun(ctx context.Context, h Handler) error

ConnectAndRun establishes a connection to the remote IRC server and sends the appropriate IRC protocol commands to begin the connection and capability negotiation.

The Handler h is called for every incoming Message parsed from the connection. Handlers are called synchronously because the ordering of incoming messages matters.

ConnectAndRun always returns an error, with one exception: if the client sends an IRC "QUIT" message followed by receiving an io.EOF from the connection, then the returned error will be nil.

Example (Reconnect)

This example deals with client disconnects. It runs the connect loop for a client in a goroutine, doubling the time between reconnect attempts each time the client exits with an error.

package main

import (
	"context"
	"log"
	"sync"
	"time"

	"github.com/Travis-Britz/irc"
)

func main() {

	client := &irc.Client{Nickname: "HelloBot"}
	connected := make(chan bool, 1)
	handler := &irc.Router{}
	handler.OnConnect(func(w irc.MessageWriter, m *irc.Message) {
		select {
		case connected <- true:
		default:
		}
		w.WriteMessage(irc.Join("#World"))
	})

	ctx := context.Background()
	wg := sync.WaitGroup{}
	wg.Add(1)
	go func() {
		defer wg.Done()

		var delay time.Duration
		for {
			select {
			case <-ctx.Done():
				return
			case <-connected:
				delay = 0
			case <-time.After(delay):
				log.Println("connecting...")
				err := client.ConnectAndRun(ctx, handler)
				log.Println("connection ended:", err)
				select {
				case <-ctx.Done():
					// after a connection ends, make sure we're not supposed to exit before looping around again.
					return
				default:
					if err != nil {
						delay = delay*2 + time.Second
					}
					log.Println("reconnect delay:", delay)
				}

			}
		}
	}()

	wg.Wait()
}
Output:

func (*Client) Nick

func (c *Client) Nick() Nickname

Nick returns the client's current nickname according to the client's internal state tracking. This is used by some route matchers to determine when a message originated from or targeted our client.

func (*Client) WriteMessage

func (c *Client) WriteMessage(m encoding.TextMarshaler)

WriteMessage implements irc.MessageWriter. It writes m to the client's connection. Marshaling errors will be reported to the client's logger. Write errors will cause the client's run method to return with the first error.

type Command

type Command string

Command is an IRC command such as PRIVMSG, NOTICE, 001, etc.

A command may also be known as the "verb", "event type", or "numeric".

func NewCTCPCmd

func NewCTCPCmd(subcommand string) Command

NewCTCPCmd returns a Command which will match the internal representation of a CTCP-encoded PRIVMSG, for mapping CTCP messages to handlers.

The returned Command is *not* a valid IRC commnad. For sending CTCP-formatted messages, see func CTCP. todo: rename this and newctcpreplycmd so it doesn't get confused with the Message builder?

func NewCTCPReplyCmd

func NewCTCPReplyCmd(subcommand string) Command

NewCTCPReplyCmd returns a Command which will match the internal representation of a CTCP-encoded NOTICE, for mapping CTCP replies to handlers.

The returned Command is *not* a valid IRC command. For sending CTCP-formatted replies, see func CTCPReply.

func (Command) String

func (c Command) String() string

String implements fmt.Stringer

type Handler

type Handler interface {
	SpeakIRC(MessageWriter, *Message)
}

A Handler responds to an IRC message.

An IRC message may be any type, including PRIVMSG, NOTICE, JOIN, Numerics, etc. It is up to the calling function to map incoming messages/commands to the appropriate handler.

Handlers should avoid modifying the provided Message.

type HandlerFunc

type HandlerFunc func(MessageWriter, *Message)

The HandlerFunc type is an adapter to allow the usage of ordinary functions as handlers, following the same pattern as http.HandlerFunc.

func (HandlerFunc) SpeakIRC

func (f HandlerFunc) SpeakIRC(w MessageWriter, m *Message)

SpeakIRC calls f(w, m).

type Message

type Message struct {

	// Tags contains IRCv3 message tags.
	// Tags are included by the server if the message-tags capability has been negotiated.
	Tags Tags

	// Source is where the message originated from.
	// It's set by the prefix portion of an IRC message.
	//
	// Source should be left empty for messages that will be written to an IRC connection.
	// See the docs for [Message.MarshalText] for more details.
	Source Prefix

	// Command is the IRC verb or numeric such as PRIVMSG, NOTICE, 001, etc.
	// It may also sometimes be referred to as the event type.
	Command Command

	// Params contains all the message parameters.
	// If a message included a trailing component,
	// it will be included without special treatment.
	// For outgoing messages,
	// only the last parameter may contain a SPACE (ascii 32).
	// Including a space in any other parameter will result in undefined behavior.
	Params Params
	// contains filtered or unexported fields
}

Message represents any incoming or outgoing IRC line.

Background

IRC is a line-delimited, text-based protocol consisting of incoming and outgoing messages. The terms "message", "line", or "event" might be used within this package to refer to a Message (although "event" usually only refers to an incoming message).

A message consists of four parts: tags, prefix, verb, and params.

func CTCP

func CTCP(target, command, message string) *Message

CTCP constructs a CTCP (Client-to-Client Protocol) encoded message to the target. command is the CTCP subcommand.

func CTCPReply

func CTCPReply(target, command, message string) *Message

CTCPReply constructs a message encoded in the CTCP reply format. target should be the nickname that sent us a CTCP message, command is the subcommand that was sent to us, and message depends on the type of query.

func Cap

func Cap(args ...string) *Message

Cap sends a CAP command as part of capability negotiation. args are the subcommand and parameters of the CAP command.

func CapEnd

func CapEnd() *Message

CapEnd ends the capability negotiation.

func CapLS

func CapLS(version string) *Message

CapLS requests a list of the capabilities supported by the server.

version is the capability negotiation protocol version, e.g. "302" for version 3.2.

func CapList

func CapList() *Message

CapList requests a list of the capabilities which have been negotiated and enabled for the client's connection.

func CapReq

func CapReq(cap string) *Message

CapReq requests capability cap be enabled for the client's connection.

func Describe

func Describe(target, action string) *Message

Describe constructs a new Message of type CTCP ACTION, with target being the intended target channel or nickname, and message being the text body.

Describe is equivalent to the "/me" or "/describe" commands that one might enter into the text input field of popular IRC clients.

By convention, actions are written in third-person.

Actions are often displayed with different formatting from regular messages. It is common for clients to display actions with italicised text and use a different color, and sometimes prefix the message with an asterisk followed by the user's nickname. The specific display formatting varies depending on which client program each user is connecting with.

For example, compare an action message with a regular privmsg:

Describe("#foo", "slaps Bob around a bit with a large trout")
Msg("#foo", "take that!")

is equivalent to typing

/me slaps Bob around a bit with a large trout
take that!

in channel #foo on most IRC clients, and might be displayed by a receiving client as

  • Alice slaps Bob around a bit with a large trout <Alice> take that!

but with italics and possibly colorized.

func Invite

func Invite(nick, channel string) *Message

Invite constructs a command to invite nick to channel.

func Join

func Join(channel string) *Message

Join constructs a channel join command.

func JoinWithKey

func JoinWithKey(channel, key string) *Message

JoinWithKey constructs a channel join command for channels that require a key (channel mode +k is set).

func Kick

func Kick(channel, nick string) *Message

Kick constructs a command to kick another user from a channel.

func KickWithReason

func KickWithReason(channel, nick, reason string) *Message

KickWithReason is similar to Kick, but the kick message will display reason.

func Mode

func Mode(target, flag, flagParam string) *Message

Mode constructs a command to change a mode on a channel or on our client connection.

func ModeQuery

func ModeQuery(target string) *Message

ModeQuery constructs a command to get the current modes of target.

func Msg

func Msg(target, message string) *Message

Msg constructs a new Message of type PRIVMSG, with target being the intended target channel or nickname, and message being the text body.

func NewMessage

func NewMessage(cmd Command, args ...string) *Message

NewMessage constructs a new Message to be sent on the connection with cmd as the verb and args as the message parameters.

Only the last argument may contain SPACE (ascii 32, %x20). This is a limitation defined in the IRC protocol. Including SPACE in any other argument will result in undefined behavior.

It is common to use '*' in place of an unused parameter. This has the benefit of matching all cases in situations where a wildcard match is allowed.

Example (AttachingTags)

The Message returned by NewMessage does not have any tags set. This also includes the Message returned by the Msg, Notice, and other related functions.

To attach tags for an outgoing message, simply access the Tags field and call the Set method before passing the message to a MessageWriter.

package main

import (
	"github.com/Travis-Britz/irc"
)

func main() {

	// h := irc.HandlerFunc(func(w irc.MessageWriter, m *irc.Message) {
	response := irc.Msg("#somechannel", "hello!")
	response.Tags.Set("msgid", "63E1033A051D4B41B1AB1FA3CF4B243E")
	//	w.WriteMessage(response)
	// })

}
Output:

func Nick

func Nick(name string) *Message

Nick constructs a nickname change command.

func Notice

func Notice(target, message string) *Message

Notice constructs a new message of type NOTICE, with target being the intended target channel or nickname, and message being the text body.

func Part

func Part(channel string) *Message

Part constructs leave (depart) command for channel.

func PartAll

func PartAll() *Message

PartAll constructs a command to leave all channels.

func PartWithReason

func PartWithReason(channel, reason string) *Message

PartWithReason is the same as Part, but with a message that may be shown to other clients

func Pass

func Pass(password string) *Message

Pass specifies the connection password.

func Ping

func Ping(message string) *Message

Ping constructs a command to PING the connection. The server will typically respond with PONG <message>, although it is possible on some networks to ping a specific server, in which case the original message is not returned.

Ping is not the same as a CTCP ping, which is sent to a client or channel via a PRIVMSG command instead. To build a CTCP ping, use CTCP(<target>, "PING", time.Now()). Replies will match a Message of type CTCPReply(<yournick>, "PING", <sent timestamp>).

func Pong

func Pong(reply string) *Message

Pong builds the reply to a PING from the connection. The reply message must be the same as the original PING message.

func Quit

func Quit(message string) *Message

Quit constructs a command that will cause the server to terminate the client's connection, and may display the quit message to clients that are configured to show quit messages.

func TagMsg

func TagMsg(tags map[string]string) *Message

TagMsg constructs a TAGMSG command, defined in the IRCv3 message-tags capability.

func User

func User(user, realname string) *Message

User is used at the beginning of a connection to specify the username and realname of a new user.

realname may contain spaces.

https://tools.ietf.org/html/rfc2812#section-3.1.3

func (*Message) Chan

func (m *Message) Chan() (string, error)

Chan returns the channel a message applies to. In the case of query messages, Chan will return an empty string. If the message target was a channel name prefixed with membership prefixes ('@', '+', etc.) the prefixes will be stripped.

func (*Message) IncludePrefix

func (m *Message) IncludePrefix()

IncludePrefix controls whether the Source field will be marshaled by MarshalText. todo: wording The Source field will be included in the encoded text for the sake of compatibility with encoding.TextUnmarshaler. However, the Source field should be left empty for messages which are written to an IRC connection. This is because [RFC 1459] states that for messages originating from a client, it is invalid to include any prefix other than the client's nickname. The RFC also instructs servers to silently discard messages which do not follow this rule.

[RFC 1459]: https://datatracker.ietf.org/doc/html/rfc1459#section-2.3 todo: rename method The default is to enable this setting for received messages and disable it for new messages. Generally this should not be needed except in the case of middleware cloning a message and passing the copy to the next handler.

func (*Message) MarshalText

func (m *Message) MarshalText() ([]byte, error)

MarshalText implements encoding.TextMarshaler, mainly for use with irc.MessageWriter.

func (*Message) Target

func (m *Message) Target() (string, error)

Target is the target of the message, which will be the current nickname of the client in the case of direct messages (queries), or the channel name if sent to a channel, or a prefix followed by the channel name if sent to a specific group of users in a channel, e.g. "+#foo" for all users on a channel with +v or higher.

func (*Message) Text

func (m *Message) Text() (string, error)

Text returns the free-form text portion of a message for the well-known (named) IRC commands. An error is returned if the method is called for unsupported message types. If err is not nil, then Text will contain the entire parameter list joined together as one string. However, for commands that return an error, it may be better to call Params.Get directly.

Supported commands include PRIVMSG, NOTICE, PART, QUIT, ERROR, and more.

In the case of PART and KICK, Text contains the <reason> message parameter.

The error may be discarded without checking If it's known that the message will always be a supported command, for example when used inside a handler that is only ever called for PRIVMSG events, then it is safe to discard err. Errors are only returned to prevent the method from returning unexpected results to callers that assume it will work for all message types.

func (*Message) UnmarshalText

func (m *Message) UnmarshalText(text []byte) error

UnmarshalText implements encoding.TextUnmarshaler, accepting a line read from an IRC stream. text should not include the trailing CR-LF pair.

This will unmarshal an arbitrarily long sequence of bytes. Length limitations should be implemented at the scanner.

type MessageWriter

type MessageWriter interface {

	// WriteMessage writes the message to the client's outgoing message queue.
	// The given encoding.TextMarshaler MUST return a byte slice which conforms to the IRC protocol.
	// If the slice does not end in "\r\n", then the sequence will be appended.
	//
	// The returned slice from the MarshalText method will be written to the connection with a single call to Write.
	// If a type implements message splitting for long messages,
	// then the entire slice must consist of multiple valid "\r\n"-delimited IRC messages.
	//
	// For example:
	//  "PRIVMSG #foo :supercalifragilisticexpi-\r\nPRIVMSG #foo :alidocious\r\n"
	//
	// It is the responsibility of the MarshalText method implementer to ensure that messages are formatted correctly,
	// and in the case of custom message splitting and continuation,
	// that flood limits are not reached.
	WriteMessage(encoding.TextMarshaler)
}

MessageWriter contains methods for sending IRC messages to a server.

type Nickname

type Nickname string

func (Nickname) Is

func (n Nickname) Is(other string) bool

Is determines whether a nickname matches a string by using Unicode case folding. Equal comparison does not currently factor in

func (Nickname) String

func (n Nickname) String() string

type Params

type Params []string

Params contains the slice of arguments for a message.

Prefer the Get method for reading params rather than accessing the slice directly.

For outgoing messages, only the last parameter may contain SPACE (ascii 32). Including SPACE in any other parameter will result in undefined behavior.

If a message included a trailing component as defined in RFC 1459, it will be included as a normal parameter.

Example (Get)

This example demonstrates why using the Get method of a Params type is preferable to accessing its slice index directly. Note the parsing behavior around missing and empty params. The parser only interprets syntax without understanding the semantics of a PART command. In other words, it does not know how many parameters a PART command has. Similarly, functions which interpret a PART command don't care about the protocol syntax difference between omitting a parameter or leaving it empty: in both cases they would only care about checking if the second param is equal to empty string.

package main

import (
	"fmt"
	"log"

	"github.com/Travis-Britz/irc"
)

func main() {

	lines := []struct {
		raw         string
		description string
	}{{
		raw:         ":WiZ PART #foo",
		description: "PART with omitted reason",
	}, {
		raw:         ":WiZ PART #foo :",
		description: "PART with empty reason",
	}, {
		raw:         ":WiZ PART #foo :leaving now",
		description: `PART with reason "leaving now"`,
	},
	}

	m := &irc.Message{}
	for _, line := range lines {
		err := m.UnmarshalText([]byte(line.raw))
		if err != nil {
			log.Println(err)
		}
		fmt.Printf("%s:\n", line.description)
		fmt.Printf("parsed: %#v\n", m.Params)
		fmt.Printf("get 1,2: %q, %q\n", m.Params.Get(1), m.Params.Get(2))
	}
}
Output:

PART with omitted reason:
parsed: irc.Params{"#foo"}
get 1,2: "#foo", ""
PART with empty reason:
parsed: irc.Params{"#foo", ""}
get 1,2: "#foo", ""
PART with reason "leaving now":
parsed: irc.Params{"#foo", "leaving now"}
get 1,2: "#foo", "leaving now"

func (Params) Get

func (p Params) Get(n int) string

Get returns the nth parameter (starting at 1) from the parameters list, or "" (empty string) if it did not exist.

Because parameters have meaning based on their position in the argument list, and because the meaning and position depends on which command/verb was used, Get does not differentiate between missing and empty parameters. Callers do not need to worry whether a parameter exists or not; they may simply check whether ordinal parameter n is equivalent to empty string. todo: translate that paragraph to english.

type Prefix

type Prefix struct {
	Nick Nickname
	User string
	Host string
}

Prefix is the optional message (line) prefix, which indicates the source (user or server) of the message, depending on the prefix format.

Example line with no prefix:

PING :86F3E357

Example nickname-only prefix:

:Travis MODE Travis :+ixz

Example "fulladdress" prefix:

:NickServ!services@services.host NOTICE Travis :This nickname is registered...

Example server prefix:

:fiery.ca.us.SwiftIRC.net MODE #foo +nt

func (Prefix) IsServer

func (p Prefix) IsServer() bool

IsServer returns true when the message originated from a server (as opposed to a user/client). When true, the server name will be contained in the Host field.

func (Prefix) String

func (p Prefix) String() string

String implements fmt.Stringer

type Router

type Router struct {
	// contains filtered or unexported fields
}

Router provides a Handler which can match incoming messages against a slice of route handlers. Matching is based on message attributes such as the command (verb), source, message contents, and more.

Routes are currently tested in the order they were added, and only the first matching route's handler will be called. However, this behavior may change in the future to allow for more efficient route matching. Therefore, care should be taken to avoid adding multiple routes which may trigger on the same input message.

func (*Router) Handle

func (r *Router) Handle(cmd Command, h Handler) *route

Handle appends h to the list of handlers for cmd.

func (*Router) HandleFunc

func (r *Router) HandleFunc(cmd Command, f HandlerFunc) *route

HandleFunc appends f to the list of handlers for cmd.

func (*Router) OnAction

func (r *Router) OnAction(wildtext string, h HandlerFunc) *route

OnAction attaches a handler for PRIVMSG that matches CTCP ACTION, and follows the same format as OnText.

func (*Router) OnCTCP

func (r *Router) OnCTCP(subcommand string, h HandlerFunc) *route

OnCTCP attaches a route handler that matches against a CTCP message of type subcommand.

func (*Router) OnCTCPReply

func (r *Router) OnCTCPReply(subcommand string, h HandlerFunc) *route

OnCTCPReply attaches a route handler that matches against a CTCP Reply of type subcommand.

func (*Router) OnConnect

func (r *Router) OnConnect(h HandlerFunc) *route

OnConnect attaches a handler which is called upon successful connection to an IRC server, after capability negotiation is complete (on servers which support capability negotiation). More specifically, it is triggered by numeric 001 (RPL_WELCOME).

func (*Router) OnError

func (r *Router) OnError(h HandlerFunc) *route

OnError is triggered when the server sends an ERROR message, usually on disconnect.

func (*Router) OnJoin

func (r *Router) OnJoin(h HandlerFunc) *route

OnJoin attaches a handler for JOIN events.

func (*Router) OnNick

func (r *Router) OnNick(h func(nick Nickname, newnick Nickname)) *route

OnNick attaches a handler when a user's nickname changes.

func (*Router) OnNotice

func (r *Router) OnNotice(wildtext string, h HandlerFunc) *route

OnNotice is triggered when a NOTICE is received from a client on the server, following the same format as OnText. For server notices, use MatchServer.

func (*Router) OnOp

func (r *Router) OnOp(h HandlerFunc) *route

func (*Router) OnPart

func (r *Router) OnPart(h HandlerFunc) *route

OnPart is triggered when a client departs a channel we are on.

func (*Router) OnQuit

func (r *Router) OnQuit(h HandlerFunc) *route

OnQuit is triggered when a client which shares a channel with us disconnects from the server.

func (*Router) OnText

func (r *Router) OnText(wildtext string, h HandlerFunc) *route

OnText attaches a handler for PRIVMSG events that match text. text is a wildcard string:

  • matches any text & matches any word ? matches a single character text matches if exact match text* matches if text starts with word *text matches if text ends with word *text* matches if text is anywhere

func (*Router) OnTextRE

func (r *Router) OnTextRE(expr string, h HandlerFunc) *route

OnTextRE attaches the handler h for PRIVMSG events that match the Go regular expression expr.

func (*Router) SpeakIRC

func (r *Router) SpeakIRC(mw MessageWriter, m *Message)

SpeakIRC implements Handler

func (*Router) Use

func (r *Router) Use(middlewares ...middleware)

Use appends global middleware to the router. Middleware are functions which accept a handler and return a handler.

Global middleware are run against every incoming line, even if there were no matching routes for the message.

Middleware can do many things:

  • Mutate incoming messages before passing them to the next Handler
  • Decorate the MessageWriter with additional functionality before passing it to the next Handler
  • Write messages to the MessageWriter
  • Prevent additional processing by not calling the next Handler

These are very powerful abilities, but it is very easy to use them improperly.

Middleware will execute in the order they were attached.

type Tags

type Tags map[string]string

Tags represents the IRCv3 message tags for an incoming or outgoing IRC line.

func (Tags) Get

func (t Tags) Get(key string) string

Get will get the message tag value for key. All variations of missing or empty values return an empty string. To check whether a message included a specific tag key, use Has.

func (Tags) Has

func (t Tags) Has(key string) bool

Has returns true when the given key was listed in the IRCv3 message tags.

func (*Tags) Set

func (t *Tags) Set(k string, v string)

Set will set the tag key k with value v.

Directories

Path Synopsis
Package ircdebug contains helper functions that are useful while writing an IRC client.
Package ircdebug contains helper functions that are useful while writing an IRC client.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL