diff options
| author | Christine Dodrill <me@christine.website> | 2018-10-19 06:24:23 -0700 |
|---|---|---|
| committer | Christine Dodrill <me@christine.website> | 2018-10-19 06:28:37 -0700 |
| commit | 273b48a8b409126e14f8beb23cee4255d1f08a2c (patch) | |
| tree | feb316d464d92e143732ca3eac525fe4ca3cda1d /vendor/github.com/belak | |
| parent | 456deb2bbadbcc8fd218a2297ac4879c069ef0ba (diff) | |
| download | x-273b48a8b409126e14f8beb23cee4255d1f08a2c.tar.xz x-273b48a8b409126e14f8beb23cee4255d1f08a2c.zip | |
GOPROXY means we can avoid vendoring
Diffstat (limited to 'vendor/github.com/belak')
| -rw-r--r-- | vendor/github.com/belak/irc/.gitignore | 3 | ||||
| -rw-r--r-- | vendor/github.com/belak/irc/.gitmodules | 3 | ||||
| -rw-r--r-- | vendor/github.com/belak/irc/.travis.yml | 21 | ||||
| -rw-r--r-- | vendor/github.com/belak/irc/LICENSE | 18 | ||||
| -rw-r--r-- | vendor/github.com/belak/irc/README.md | 104 | ||||
| -rw-r--r-- | vendor/github.com/belak/irc/client.go | 331 | ||||
| -rw-r--r-- | vendor/github.com/belak/irc/client_handlers.go | 151 | ||||
| -rw-r--r-- | vendor/github.com/belak/irc/conn.go | 103 | ||||
| -rw-r--r-- | vendor/github.com/belak/irc/handler.go | 16 | ||||
| -rw-r--r-- | vendor/github.com/belak/irc/parser.go | 394 | ||||
| -rw-r--r-- | vendor/github.com/belak/irc/utils.go | 50 |
11 files changed, 0 insertions, 1194 deletions
diff --git a/vendor/github.com/belak/irc/.gitignore b/vendor/github.com/belak/irc/.gitignore deleted file mode 100644 index b64fa18..0000000 --- a/vendor/github.com/belak/irc/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.cover -*.test -*.out diff --git a/vendor/github.com/belak/irc/.gitmodules b/vendor/github.com/belak/irc/.gitmodules deleted file mode 100644 index 8ed3899..0000000 --- a/vendor/github.com/belak/irc/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "testcases"] - path = testcases - url = https://github.com/go-irc/irc-parser-tests/ diff --git a/vendor/github.com/belak/irc/.travis.yml b/vendor/github.com/belak/irc/.travis.yml deleted file mode 100644 index 494bd20..0000000 --- a/vendor/github.com/belak/irc/.travis.yml +++ /dev/null @@ -1,21 +0,0 @@ -language: go - -before_install: - - go get github.com/axw/gocov/gocov - - go get github.com/mattn/goveralls - - go get golang.org/x/tools/cmd/cover - # Grab all deps (should just be test deps) - - go get -v -t ./... - # Linting deps - - go get github.com/alecthomas/gometalinter - - gometalinter --install - # Remove the go file from the test cases dir as it fails linting - - rm ./testcases/*.go - -script: - - gometalinter --fast ./... -D gas - - go test -race -v ./... - - go test -covermode=count -coverprofile=profile.cov - -after_script: - - $HOME/gopath/bin/goveralls -coverprofile=profile.cov -service=travis-ci diff --git a/vendor/github.com/belak/irc/LICENSE b/vendor/github.com/belak/irc/LICENSE deleted file mode 100644 index 7e5e1ab..0000000 --- a/vendor/github.com/belak/irc/LICENSE +++ /dev/null @@ -1,18 +0,0 @@ -Copyright 2016 Kaleb Elwert - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/belak/irc/README.md b/vendor/github.com/belak/irc/README.md deleted file mode 100644 index f2d755b..0000000 --- a/vendor/github.com/belak/irc/README.md +++ /dev/null @@ -1,104 +0,0 @@ -# go-irc - -[](https://godoc.org/github.com/go-irc/irc) -[](https://travis-ci.org/go-irc/irc) -[](https://coveralls.io/github/go-irc/irc?branch=master) - -This package was originally created to only handle message parsing, -but has since been expanded to include a small abstraction around a -connection and a simple client. - -This library is not designed to hide any of the IRC elements from -you. If you just want to build a simple chat bot and don't want to -deal with IRC in particular, there are a number of other libraries -which provide a more full featured client if that's what you're -looking for. - -This library is meant to stay as simple as possible so it can be a -building block for other packages. - -This library aims for API compatibility whenever possible. New -functions and other additions will most likely not result in a major -version increase unless they break the API. This library aims to -follow the semver recommendations mentioned on gopkg.in. - -Due to complications in how to support x/net/context vs the built-in context -package, only go 1.7+ is officially supported. - -## Import Paths - -All development happens on the `master` branch and when features are -considered stable enough, a new release will be tagged. - -As a result of this, there are multiple import locations. - -* `gopkg.in/irc.v2` should be used to develop against the commits - tagged as stable -* `github.com/go-irc/irc` should be used to develop against the master branch - -## Development - -In order to run the tests, make sure all submodules are up to date. If you are -just using this library, these are not needed. - -## Example - -```go -package main - -import ( - "log" - "net" - - "github.com/belak/irc" -) - -func main() { - conn, err := net.Dial("tcp", "chat.freenode.net:6667") - if err != nil { - log.Fatalln(err) - } - - config := irc.ClientConfig{ - Nick: "i_have_a_nick", - Pass: "password", - User: "username", - Name: "Full Name", - Handler: irc.HandlerFunc(func(c *irc.Client, m *irc.Message) { - if m.Command == "001" { - // 001 is a welcome event, so we join channels there - c.Write("JOIN #bot-test-chan") - } else if m.Command == "PRIVMSG" && m.FromChannel() { - // Create a handler on all messages. - c.WriteMessage(&irc.Message{ - Command: "PRIVMSG", - Params: []string{ - m.Params[0], - m.Trailing(), - }, - }) - } - }), - } - - // Create the client - client := irc.NewClient(conn, config) - err = client.Run() - if err != nil { - log.Fatalln(err) - } -} -``` - -## Major Version Changes - -### v1 - -Initial release - -### v2 - -- CTCP messages will no longer be rewritten. The decision was made that this - library should pass through all messages without mangling them. -- Remove Message.FromChannel as this is not always accurate, while - Client.FromChannel should always be accurate. diff --git a/vendor/github.com/belak/irc/client.go b/vendor/github.com/belak/irc/client.go deleted file mode 100644 index 999ebff..0000000 --- a/vendor/github.com/belak/irc/client.go +++ /dev/null @@ -1,331 +0,0 @@ -package irc - -import ( - "context" - "errors" - "fmt" - "io" - "sync" - "time" -) - -// ClientConfig is a structure used to configure a Client. -type ClientConfig struct { - // General connection information. - Nick string - Pass string - User string - Name string - - // Connection settings - PingFrequency time.Duration - PingTimeout time.Duration - - // SendLimit is how frequent messages can be sent. If this is zero, - // there will be no limit. - SendLimit time.Duration - - // SendBurst is the number of messages which can be sent in a burst. - SendBurst int - - // Handler is used for message dispatching. - Handler Handler -} - -type cap struct { - // Requested means that this cap was requested by the user - Requested bool - - // Required will be true if this cap is non-optional - Required bool - - // Enabled means that this cap was accepted by the server - Enabled bool - - // Available means that the server supports this cap - Available bool -} - -// Client is a wrapper around Conn which is designed to make common operations -// much simpler. -type Client struct { - *Conn - config ClientConfig - - // Internal state - currentNick string - limiter chan struct{} - incomingPongChan chan string - errChan chan error - caps map[string]cap - remainingCapResponses int - connected bool -} - -// NewClient creates a client given an io stream and a client config. -func NewClient(rw io.ReadWriter, config ClientConfig) *Client { - c := &Client{ - Conn: NewConn(rw), - config: config, - errChan: make(chan error, 1), - caps: make(map[string]cap), - } - - // Replace the writer writeCallback with one of our own - c.Conn.Writer.writeCallback = c.writeCallback - - return c -} - -func (c *Client) writeCallback(w *Writer, line string) error { - if c.limiter != nil { - <-c.limiter - } - - _, err := w.writer.Write([]byte(line + "\r\n")) - if err != nil { - c.sendError(err) - } - return err -} - -// maybeStartLimiter will start a ticker which will limit how quickly messages -// can be written to the connection if the SendLimit is set in the config. -func (c *Client) maybeStartLimiter(wg *sync.WaitGroup, exiting chan struct{}) { - if c.config.SendLimit == 0 { - return - } - - wg.Add(1) - - // If SendBurst is 0, this will be unbuffered, so keep that in mind. - c.limiter = make(chan struct{}, c.config.SendBurst) - limitTick := time.NewTicker(c.config.SendLimit) - - go func() { - defer wg.Done() - - var done bool - for !done { - select { - case <-limitTick.C: - select { - case c.limiter <- struct{}{}: - default: - } - case <-exiting: - done = true - } - } - - limitTick.Stop() - close(c.limiter) - c.limiter = nil - }() -} - -// maybeStartPingLoop will start a goroutine to send out PING messages at the -// PingFrequency in the config if the frequency is not 0. -func (c *Client) maybeStartPingLoop(wg *sync.WaitGroup, exiting chan struct{}) { - if c.config.PingFrequency <= 0 { - return - } - - wg.Add(1) - - c.incomingPongChan = make(chan string, 5) - - go func() { - defer wg.Done() - - pingHandlers := make(map[string]chan struct{}) - ticker := time.NewTicker(c.config.PingFrequency) - - defer ticker.Stop() - - for { - select { - case <-ticker.C: - // Each time we get a tick, we send off a ping and start a - // goroutine to handle the pong. - timestamp := time.Now().Unix() - pongChan := make(chan struct{}, 1) - pingHandlers[fmt.Sprintf("%d", timestamp)] = pongChan - wg.Add(1) - go c.handlePing(timestamp, pongChan, wg, exiting) - case data := <-c.incomingPongChan: - // Make sure the pong gets routed to the correct - // goroutine. - - c := pingHandlers[data] - delete(pingHandlers, data) - - if c != nil { - c <- struct{}{} - } - case <-exiting: - return - } - } - }() -} - -func (c *Client) handlePing(timestamp int64, pongChan chan struct{}, wg *sync.WaitGroup, exiting chan struct{}) { - defer wg.Done() - - c.Writef("PING :%d", timestamp) - - timer := time.NewTimer(c.config.PingTimeout) - defer timer.Stop() - - select { - case <-timer.C: - c.sendError(errors.New("Ping Timeout")) - case <-pongChan: - return - case <-exiting: - return - } -} - -// maybeStartCapHandshake will run a CAP LS and all the relevant CAP REQ -// commands if there are any CAPs requested. -func (c *Client) maybeStartCapHandshake() { - if len(c.caps) <= 0 { - return - } - - c.Write("CAP LS") - c.remainingCapResponses = 1 // We count the CAP LS response as a normal response - for key, cap := range c.caps { - if cap.Requested { - c.Writef("CAP REQ :%s", key) - c.remainingCapResponses++ - } - } -} - -// CapRequest allows you to request IRCv3 capabilities from the server during -// the handshake. The behavior is undefined if this is called before the -// handshake completes so it is recommended that this be called before Run. If -// the CAP is marked as required, the client will exit if that CAP could not be -// negotiated during the handshake. -func (c *Client) CapRequest(capName string, required bool) { - cap := c.caps[capName] - cap.Requested = true - cap.Required = cap.Required || required - c.caps[capName] = cap -} - -// CapEnabled allows you to check if a CAP is enabled for this connection. Note -// that it will not be populated until after the CAP handshake is done, so it is -// recommended to wait to check this until after a message like 001. -func (c *Client) CapEnabled(capName string) bool { - return c.caps[capName].Enabled -} - -// CapAvailable allows you to check if a CAP is available on this server. Note -// that it will not be populated until after the CAP handshake is done, so it is -// recommended to wait to check this until after a message like 001. -func (c *Client) CapAvailable(capName string) bool { - return c.caps[capName].Available -} - -func (c *Client) sendError(err error) { - select { - case c.errChan <- err: - default: - } -} - -func (c *Client) startReadLoop(wg *sync.WaitGroup) { - wg.Add(1) - - go func() { - defer wg.Done() - - for { - m, err := c.ReadMessage() - if err != nil { - c.sendError(err) - break - } - - if f, ok := clientFilters[m.Command]; ok { - f(c, m) - } - - if c.config.Handler != nil { - c.config.Handler.Handle(c, m) - } - } - - }() -} - -// Run starts the main loop for this IRC connection. Note that it may break in -// strange and unexpected ways if it is called again before the first connection -// exits. -func (c *Client) Run() error { - return c.RunContext(context.TODO()) -} - -// RunContext is the same as Run but a context.Context can be passed in for -// cancelation. -func (c *Client) RunContext(ctx context.Context) error { - // exiting is used by the main goroutine here to ensure any sub-goroutines - // get closed when exiting. - exiting := make(chan struct{}) - var wg sync.WaitGroup - - c.maybeStartLimiter(&wg, exiting) - c.maybeStartPingLoop(&wg, exiting) - - c.currentNick = c.config.Nick - - if c.config.Pass != "" { - c.Writef("PASS :%s", c.config.Pass) - } - - c.maybeStartCapHandshake() - - // This feels wrong because it results in CAP LS, CAP REQ, NICK, USER, CAP - // END, but it works and lets us keep the code a bit simpler. - c.Writef("NICK :%s", c.config.Nick) - c.Writef("USER %s 0.0.0.0 0.0.0.0 :%s", c.config.User, c.config.Name) - - // Now that the handshake is pretty much done, we can start listening for - // messages. - c.startReadLoop(&wg) - - // Wait for an error from any goroutine or for the context to time out, then - // signal we're exiting and wait for the goroutines to exit. - var err error - select { - case err = <-c.errChan: - case <-ctx.Done(): - } - - close(exiting) - wg.Wait() - - return err -} - -// CurrentNick returns what the nick of the client is known to be at this point -// in time. -func (c *Client) CurrentNick() string { - return c.currentNick -} - -// FromChannel takes a Message representing a PRIVMSG and returns if that -// message came from a channel or directly from a user. -func (c *Client) FromChannel(m *Message) bool { - if len(m.Params) < 1 { - return false - } - - // The first param is the target, so if this doesn't match the current nick, - // the message came from a channel. - return m.Params[0] != c.currentNick -} diff --git a/vendor/github.com/belak/irc/client_handlers.go b/vendor/github.com/belak/irc/client_handlers.go deleted file mode 100644 index 3b5cd8d..0000000 --- a/vendor/github.com/belak/irc/client_handlers.go +++ /dev/null @@ -1,151 +0,0 @@ -package irc - -import ( - "fmt" - "strings" -) - -type clientFilter func(*Client, *Message) - -// clientFilters are pre-processing which happens for certain message -// types. These were moved from below to keep the complexity of each -// component down. -var clientFilters = map[string]clientFilter{ - "001": handle001, - "433": handle433, - "437": handle437, - "PING": handlePing, - "PONG": handlePong, - "NICK": handleNick, - "CAP": handleCap, -} - -// From rfc2812 section 5.1 (Command responses) -// -// 001 RPL_WELCOME -// "Welcome to the Internet Relay Network -// <nick>!<user>@<host>" -func handle001(c *Client, m *Message) { - c.currentNick = m.Params[0] - c.connected = true -} - -// From rfc2812 section 5.2 (Error Replies) -// -// 433 ERR_NICKNAMEINUSE -// "<nick> :Nickname is already in use" -// -// - Returned when a NICK message is processed that results -// in an attempt to change to a currently existing -// nickname. -func handle433(c *Client, m *Message) { - // We only want to try and handle nick collisions during the initial - // handshake. - if c.connected { - return - } - c.currentNick = c.currentNick + "_" - c.Writef("NICK :%s", c.currentNick) -} - -// From rfc2812 section 5.2 (Error Replies) -// -// 437 ERR_UNAVAILRESOURCE -// "<nick/channel> :Nick/channel is temporarily unavailable" -// -// - Returned by a server to a user trying to join a channel -// currently blocked by the channel delay mechanism. -// -// - Returned by a server to a user trying to change nickname -// when the desired nickname is blocked by the nick delay -// mechanism. -func handle437(c *Client, m *Message) { - // We only want to try and handle nick collisions during the initial - // handshake. - if c.connected { - return - } - c.currentNick = c.currentNick + "_" - c.Writef("NICK :%s", c.currentNick) -} - -func handlePing(c *Client, m *Message) { - reply := m.Copy() - reply.Command = "PONG" - c.WriteMessage(reply) -} - -func handlePong(c *Client, m *Message) { - if c.incomingPongChan != nil { - select { - case c.incomingPongChan <- m.Trailing(): - default: - // Note that this return isn't really needed, but it helps some code - // coverage tools actually see this line. - return - } - } -} - -func handleNick(c *Client, m *Message) { - if m.Prefix.Name == c.currentNick && len(m.Params) > 0 { - c.currentNick = m.Params[0] - } -} - -var capFilters = map[string]clientFilter{ - "LS": handleCapLs, - "ACK": handleCapAck, - "NAK": handleCapNak, -} - -func handleCap(c *Client, m *Message) { - if c.remainingCapResponses <= 0 || len(m.Params) <= 2 { - return - } - - if filter, ok := capFilters[m.Params[1]]; ok { - filter(c, m) - } - - if c.remainingCapResponses <= 0 { - for key, cap := range c.caps { - if cap.Required && !cap.Enabled { - c.sendError(fmt.Errorf("CAP %s requested but not accepted", key)) - return - } - } - - c.Write("CAP END") - } -} - -func handleCapLs(c *Client, m *Message) { - for _, key := range strings.Split(m.Trailing(), " ") { - cap := c.caps[key] - cap.Available = true - c.caps[key] = cap - } - c.remainingCapResponses-- -} - -func handleCapAck(c *Client, m *Message) { - for _, key := range strings.Split(m.Trailing(), " ") { - cap := c.caps[key] - cap.Enabled = true - c.caps[key] = cap - } - c.remainingCapResponses-- -} - -func handleCapNak(c *Client, m *Message) { - // If we got a NAK and this REQ was required, we need to bail - // with an error. - for _, key := range strings.Split(m.Trailing(), " ") { - if c.caps[key].Required { - c.sendError(fmt.Errorf("CAP %s requested but was rejected", key)) - return - } - } - c.remainingCapResponses-- -} diff --git a/vendor/github.com/belak/irc/conn.go b/vendor/github.com/belak/irc/conn.go deleted file mode 100644 index 7f9b68e..0000000 --- a/vendor/github.com/belak/irc/conn.go +++ /dev/null @@ -1,103 +0,0 @@ -package irc - -import ( - "bufio" - "fmt" - "io" -) - -// Conn represents a simple IRC client. It embeds an irc.Reader and an -// irc.Writer. -type Conn struct { - *Reader - *Writer -} - -// NewConn creates a new Conn -func NewConn(rw io.ReadWriter) *Conn { - return &Conn{ - NewReader(rw), - NewWriter(rw), - } -} - -// Writer is the outgoing side of a connection. -type Writer struct { - // DebugCallback is called for each outgoing message. The name of this may - // not be stable. - DebugCallback func(line string) - - // Internal fields - writer io.Writer - writeCallback func(w *Writer, line string) error -} - -func defaultWriteCallback(w *Writer, line string) error { - _, err := w.writer.Write([]byte(line + "\r\n")) - return err -} - -// NewWriter creates an irc.Writer from an io.Writer. -func NewWriter(w io.Writer) *Writer { - return &Writer{nil, w, defaultWriteCallback} -} - -// Write is a simple function which will write the given line to the -// underlying connection. -func (w *Writer) Write(line string) error { - if w.DebugCallback != nil { - w.DebugCallback(line) - } - - return w.writeCallback(w, line) -} - -// Writef is a wrapper around the connection's Write method and -// fmt.Sprintf. Simply use it to send a message as you would normally -// use fmt.Printf. -func (w *Writer) Writef(format string, args ...interface{}) error { - return w.Write(fmt.Sprintf(format, args...)) -} - -// WriteMessage writes the given message to the stream -func (w *Writer) WriteMessage(m *Message) error { - return w.Write(m.String()) -} - -// Reader is the incoming side of a connection. The data will be -// buffered, so do not re-use the io.Reader used to create the -// irc.Reader. -type Reader struct { - // DebugCallback is called for each incoming message. The name of this may - // not be stable. - DebugCallback func(string) - - // Internal fields - reader *bufio.Reader -} - -// NewReader creates an irc.Reader from an io.Reader. Note that once a reader is -// passed into this function, you should no longer use it as it is being used -// inside a bufio.Reader so you cannot rely on only the amount of data for a -// Message being read when you call ReadMessage. -func NewReader(r io.Reader) *Reader { - return &Reader{ - nil, - bufio.NewReader(r), - } -} - -// ReadMessage returns the next message from the stream or an error. -func (r *Reader) ReadMessage() (*Message, error) { - line, err := r.reader.ReadString('\n') - if err != nil { - return nil, err - } - - if r.DebugCallback != nil { - r.DebugCallback(line) - } - - // Parse the message from our line - return ParseMessage(line) -} diff --git a/vendor/github.com/belak/irc/handler.go b/vendor/github.com/belak/irc/handler.go deleted file mode 100644 index 6a9fca7..0000000 --- a/vendor/github.com/belak/irc/handler.go +++ /dev/null @@ -1,16 +0,0 @@ -package irc - -// Handler is a simple interface meant for dispatching a message from -// a Client connection. -type Handler interface { - Handle(*Client, *Message) -} - -// HandlerFunc is a simple wrapper around a function which allows it -// to be used as a Handler. -type HandlerFunc func(*Client, *Message) - -// Handle calls f(c, m) -func (f HandlerFunc) Handle(c *Client, m *Message) { - f(c, m) -} diff --git a/vendor/github.com/belak/irc/parser.go b/vendor/github.com/belak/irc/parser.go deleted file mode 100644 index 819de4d..0000000 --- a/vendor/github.com/belak/irc/parser.go +++ /dev/null @@ -1,394 +0,0 @@ -package irc - -import ( - "bytes" - "errors" - "strings" -) - -var tagDecodeSlashMap = map[rune]rune{ - ':': ';', - 's': ' ', - '\\': '\\', - 'r': '\r', - 'n': '\n', -} - -var tagEncodeMap = map[rune]string{ - ';': "\\:", - ' ': "\\s", - '\\': "\\\\", - '\r': "\\r", - '\n': "\\n", -} - -var ( - // ErrZeroLengthMessage is returned when parsing if the input is - // zero-length. - ErrZeroLengthMessage = errors.New("irc: Cannot parse zero-length message") - - // ErrMissingDataAfterPrefix is returned when parsing if there is - // no message data after the prefix. - ErrMissingDataAfterPrefix = errors.New("irc: No message data after prefix") - - // ErrMissingDataAfterTags is returned when parsing if there is no - // message data after the tags. - ErrMissingDataAfterTags = errors.New("irc: No message data after tags") - - // ErrMissingCommand is returned when parsing if there is no - // command in the parsed message. - ErrMissingCommand = errors.New("irc: Missing message command") -) - -// TagValue represents the value of a tag. -type TagValue string - -// ParseTagValue parses a TagValue from the connection. If you need to -// set a TagValue, you probably want to just set the string itself, so -// it will be encoded properly. -func ParseTagValue(v string) TagValue { - ret := &bytes.Buffer{} - - input := bytes.NewBufferString(v) - - for { - c, _, err := input.ReadRune() - if err != nil { - break - } - - if c == '\\' { - c2, _, err := input.ReadRune() - - // If we got a backslash then the end of the tag value, we should - // just ignore the backslash. - if err != nil { - break - } - - if replacement, ok := tagDecodeSlashMap[c2]; ok { - ret.WriteRune(replacement) - } else { - ret.WriteRune(c2) - } - } else { - ret.WriteRune(c) - } - } - - return TagValue(ret.String()) -} - -// Encode converts a TagValue to the format in the connection. -func (v TagValue) Encode() string { - ret := &bytes.Buffer{} - - for _, c := range v { - if replacement, ok := tagEncodeMap[c]; ok { - ret.WriteString(replacement) - } else { - ret.WriteRune(c) - } - } - - return ret.String() -} - -// Tags represents the IRCv3 message tags. -type Tags map[string]TagValue - -// ParseTags takes a tag string and parses it into a tag map. It will -// always return a tag map, even if there are no valid tags. -func ParseTags(line string) Tags { - ret := Tags{} - - tags := strings.Split(line, ";") - for _, tag := range tags { - parts := strings.SplitN(tag, "=", 2) - if len(parts) < 2 { - ret[parts[0]] = "" - continue - } - - ret[parts[0]] = ParseTagValue(parts[1]) - } - - return ret -} - -// GetTag is a convenience method to look up a tag in the map. -func (t Tags) GetTag(key string) (string, bool) { - ret, ok := t[key] - return string(ret), ok -} - -// Copy will create a new copy of all IRC tags attached to this -// message. -func (t Tags) Copy() Tags { - ret := Tags{} - - for k, v := range t { - ret[k] = v - } - - return ret -} - -// String ensures this is stringable -func (t Tags) String() string { - buf := &bytes.Buffer{} - - for k, v := range t { - buf.WriteByte(';') - buf.WriteString(k) - if v != "" { - buf.WriteByte('=') - buf.WriteString(v.Encode()) - } - } - - // We don't need the first byte because that's an extra ';' - // character. - buf.ReadByte() - - return buf.String() -} - -// Prefix represents the prefix of a message, generally the user who sent it -type Prefix struct { - // Name will contain the nick of who sent the message, the - // server who sent the message, or a blank string - Name string - - // User will either contain the user who sent the message or a blank string - User string - - // Host will either contain the host of who sent the message or a blank string - Host string -} - -// ParsePrefix takes an identity string and parses it into an -// identity struct. It will always return an Prefix struct and never -// nil. -func ParsePrefix(line string) *Prefix { - // Start by creating an Prefix with nothing but the host - id := &Prefix{ - Name: line, - } - - uh := strings.SplitN(id.Name, "@", 2) - if len(uh) == 2 { - id.Name, id.Host = uh[0], uh[1] - } - - nu := strings.SplitN(id.Name, "!", 2) - if len(nu) == 2 { - id.Name, id.User = nu[0], nu[1] - } - - return id -} - -// Copy will create a new copy of an Prefix -func (p *Prefix) Copy() *Prefix { - if p == nil { - return nil - } - - newPrefix := &Prefix{} - - *newPrefix = *p - - return newPrefix -} - -// String ensures this is stringable -func (p *Prefix) String() string { - buf := &bytes.Buffer{} - buf.WriteString(p.Name) - - if p.User != "" { - buf.WriteString("!") - buf.WriteString(p.User) - } - - if p.Host != "" { - buf.WriteString("@") - buf.WriteString(p.Host) - } - - return buf.String() -} - -// Message represents a line parsed from the server -type Message struct { - // Each message can have IRCv3 tags - Tags - - // Each message can have a Prefix - *Prefix - - // Command is which command is being called. - Command string - - // Params are all the arguments for the command. - Params []string -} - -// MustParseMessage calls ParseMessage and either returns the message -// or panics if an error is returned. -func MustParseMessage(line string) *Message { - m, err := ParseMessage(line) - if err != nil { - panic(err.Error()) - } - return m -} - -// ParseMessage takes a message string (usually a whole line) and -// parses it into a Message struct. This will return nil in the case -// of invalid messages. -func ParseMessage(line string) (*Message, error) { - // Trim the line and make sure we have data - line = strings.TrimRight(line, "\r\n") - if len(line) == 0 { - return nil, ErrZeroLengthMessage - } - - c := &Message{ - Tags: Tags{}, - Prefix: &Prefix{}, - } - - if line[0] == '@' { - split := strings.SplitN(line, " ", 2) - if len(split) < 2 { - return nil, ErrMissingDataAfterTags - } - - c.Tags = ParseTags(split[0][1:]) - line = split[1] - } - - if line[0] == ':' { - split := strings.SplitN(line, " ", 2) - if len(split) < 2 { - return nil, ErrMissingDataAfterPrefix - } - - // Parse the identity, if there was one - c.Prefix = ParsePrefix(split[0][1:]) - line = split[1] - } - - // Split out the trailing then the rest of the args. Because - // we expect there to be at least one result as an arg (the - // command) we don't need to special case the trailing arg and - // can just attempt a split on " :" |
