2016-11-14 11:50:14 +00:00
|
|
|
// Copyright 2016 Liam Stanley <me@liamstanley.io>. All rights reserved.
|
|
|
|
// Use of this source code is governed by the MIT license that can be
|
|
|
|
// found in the LICENSE file.
|
|
|
|
|
2016-11-13 08:30:43 +00:00
|
|
|
package girc
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2016-11-19 15:55:19 +00:00
|
|
|
"fmt"
|
2016-11-13 08:30:43 +00:00
|
|
|
"strings"
|
2016-11-23 02:58:26 +00:00
|
|
|
"time"
|
2016-11-13 08:30:43 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2017-01-03 16:02:24 +00:00
|
|
|
eventSpace byte = 0x20 // Separator.
|
|
|
|
maxLength = 510 // Maximum length is 510 (2 for line endings).
|
2016-11-13 08:30:43 +00:00
|
|
|
)
|
|
|
|
|
2016-11-19 14:36:17 +00:00
|
|
|
// cutCRFunc is used to trim CR characters from prefixes/messages.
|
|
|
|
func cutCRFunc(r rune) bool {
|
2016-11-13 08:30:43 +00:00
|
|
|
return r == '\r' || r == '\n'
|
|
|
|
}
|
|
|
|
|
|
|
|
// Event represents an IRC protocol message, see RFC1459 section 2.3.1
|
|
|
|
//
|
|
|
|
// <message> :: [':' <prefix> <SPACE>] <command> <params> <crlf>
|
|
|
|
// <prefix> :: <servername> | <nick> ['!' <user>] ['@' <host>]
|
|
|
|
// <command> :: <letter>{<letter>} | <number> <number> <number>
|
|
|
|
// <SPACE> :: ' '{' '}
|
|
|
|
// <params> :: <SPACE> [':' <trailing> | <middle> <params>]
|
|
|
|
// <middle> :: <Any *non-empty* sequence of octets not including SPACE or NUL
|
|
|
|
// or CR or LF, the first of which may not be ':'>
|
|
|
|
// <trailing> :: <Any, possibly empty, sequence of octets not including NUL or
|
|
|
|
// CR or LF>
|
|
|
|
// <crlf> :: CR LF
|
|
|
|
type Event struct {
|
2016-12-24 05:15:41 +00:00
|
|
|
Source *Source // The source of the event.
|
2017-01-03 16:02:24 +00:00
|
|
|
Tags Tags // IRCv3 style message tags. Only use if network supported.
|
2016-11-23 00:32:14 +00:00
|
|
|
Command string // the IRC command, e.g. JOIN, PRIVMSG, KILL.
|
|
|
|
Params []string // parameters to the command. Commonly nickname, channel, etc.
|
|
|
|
Trailing string // any trailing data. e.g. with a PRIVMSG, this is the message text.
|
|
|
|
EmptyTrailing bool // if true, trailing prefix (:) will be added even if Event.Trailing is empty.
|
|
|
|
Sensitive bool // if the message is sensitive (e.g. and should not be logged).
|
2016-11-13 08:30:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ParseEvent takes a string and attempts to create a Event struct.
|
2016-11-23 00:32:14 +00:00
|
|
|
//
|
2016-11-13 08:30:43 +00:00
|
|
|
// Returns nil if the Event is invalid.
|
|
|
|
func ParseEvent(raw string) (e *Event) {
|
2016-11-23 00:32:14 +00:00
|
|
|
// Ignore empty events.
|
2016-11-19 14:36:17 +00:00
|
|
|
if raw = strings.TrimFunc(raw, cutCRFunc); len(raw) < 2 {
|
2016-11-13 08:30:43 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
i, j := 0, 0
|
|
|
|
e = new(Event)
|
|
|
|
|
2017-01-03 16:02:24 +00:00
|
|
|
if raw[0] == prefixTag {
|
|
|
|
// Tags end with a space.
|
|
|
|
i = strings.IndexByte(raw, eventSpace)
|
|
|
|
|
|
|
|
if i < 2 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
e.Tags = ParseTags(raw[1:i])
|
|
|
|
raw = raw[i+1:]
|
|
|
|
}
|
|
|
|
|
|
|
|
if raw[0] == messagePrefix {
|
2016-11-23 00:32:14 +00:00
|
|
|
// Prefix ends with a space.
|
2017-01-03 16:02:24 +00:00
|
|
|
i = strings.IndexByte(raw, eventSpace)
|
2016-11-13 08:30:43 +00:00
|
|
|
|
2016-11-23 00:32:14 +00:00
|
|
|
// Prefix string must not be empty if the indicator is present.
|
2016-11-13 08:30:43 +00:00
|
|
|
if i < 2 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-11-19 14:36:17 +00:00
|
|
|
e.Source = ParseSource(raw[1:i])
|
2016-11-13 08:30:43 +00:00
|
|
|
|
2016-11-23 00:32:14 +00:00
|
|
|
// Skip space at the end of the prefix.
|
|
|
|
i++
|
2016-11-13 08:30:43 +00:00
|
|
|
}
|
|
|
|
|
2016-11-23 00:32:14 +00:00
|
|
|
// Find end of command.
|
2017-01-03 16:02:24 +00:00
|
|
|
j = i + strings.IndexByte(raw[i:], eventSpace)
|
2016-11-13 08:30:43 +00:00
|
|
|
|
2016-11-23 00:32:14 +00:00
|
|
|
// Extract command.
|
2016-11-13 14:21:54 +00:00
|
|
|
if j < i {
|
2016-11-13 08:30:43 +00:00
|
|
|
e.Command = strings.ToUpper(raw[i:])
|
|
|
|
return e
|
|
|
|
}
|
|
|
|
|
2016-11-13 14:21:54 +00:00
|
|
|
e.Command = strings.ToUpper(raw[i:j])
|
2016-11-23 00:32:14 +00:00
|
|
|
// Skip space after command.
|
|
|
|
j++
|
2016-11-13 08:30:43 +00:00
|
|
|
|
2016-11-23 00:32:14 +00:00
|
|
|
// Find prefix for trailer.
|
2017-01-03 16:02:24 +00:00
|
|
|
i = strings.IndexByte(raw[j:], messagePrefix)
|
2016-11-13 08:30:43 +00:00
|
|
|
|
2017-01-03 16:02:24 +00:00
|
|
|
if i < 0 || raw[j+i-1] != eventSpace {
|
2016-11-23 00:32:14 +00:00
|
|
|
// No trailing argument.
|
2017-01-03 16:02:24 +00:00
|
|
|
e.Params = strings.Split(raw[j:], string(eventSpace))
|
2016-11-13 08:30:43 +00:00
|
|
|
return e
|
|
|
|
}
|
|
|
|
|
2016-11-23 00:32:14 +00:00
|
|
|
// Compensate for index on substring.
|
2016-11-13 08:30:43 +00:00
|
|
|
i = i + j
|
|
|
|
|
2016-11-23 00:32:14 +00:00
|
|
|
// Check if we need to parse arguments.
|
2016-11-13 08:30:43 +00:00
|
|
|
if i > j {
|
2017-01-03 16:02:24 +00:00
|
|
|
e.Params = strings.Split(raw[j:i-1], string(eventSpace))
|
2016-11-13 08:30:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
e.Trailing = raw[i+1:]
|
|
|
|
|
2016-11-23 00:32:14 +00:00
|
|
|
// We need to re-encode the trailing argument even if it was empty.
|
2016-11-13 08:30:43 +00:00
|
|
|
if len(e.Trailing) <= 0 {
|
|
|
|
e.EmptyTrailing = true
|
|
|
|
}
|
|
|
|
|
|
|
|
return e
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-11-23 00:32:14 +00:00
|
|
|
// Len calculates the length of the string representation of event.
|
2016-11-13 08:30:43 +00:00
|
|
|
func (e *Event) Len() (length int) {
|
2017-01-03 16:02:24 +00:00
|
|
|
if e.Tags != nil {
|
|
|
|
// Include tags and trailing space.
|
|
|
|
length = e.Tags.Len() + 1
|
|
|
|
}
|
2016-11-19 14:36:17 +00:00
|
|
|
if e.Source != nil {
|
2016-11-23 00:32:14 +00:00
|
|
|
// Include prefix and trailing space.
|
2017-01-03 16:02:24 +00:00
|
|
|
length += e.Source.Len() + 2
|
2016-11-13 08:30:43 +00:00
|
|
|
}
|
|
|
|
|
2017-01-03 16:02:24 +00:00
|
|
|
length += len(e.Command)
|
2016-11-13 08:30:43 +00:00
|
|
|
|
|
|
|
if len(e.Params) > 0 {
|
2017-01-03 16:02:24 +00:00
|
|
|
length += len(e.Params)
|
2016-11-13 08:30:43 +00:00
|
|
|
|
2016-11-13 14:21:54 +00:00
|
|
|
for i := 0; i < len(e.Params); i++ {
|
2017-01-03 16:02:24 +00:00
|
|
|
length += len(e.Params[i])
|
2016-11-13 08:30:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(e.Trailing) > 0 || e.EmptyTrailing {
|
2016-11-23 00:32:14 +00:00
|
|
|
// Include prefix and space.
|
2017-01-03 16:02:24 +00:00
|
|
|
length += len(e.Trailing) + 2
|
2016-11-13 08:30:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-11-23 00:32:14 +00:00
|
|
|
// Bytes returns a []byte representation of event. Strips all newlines and
|
|
|
|
// carriage returns.
|
2016-11-13 08:30:43 +00:00
|
|
|
//
|
2016-11-23 00:32:14 +00:00
|
|
|
// Per RFC2812 section 2.3, messages should not exceed 512 characters in
|
|
|
|
// length. This method forces that limit by discarding any characters
|
2016-11-13 08:30:43 +00:00
|
|
|
// exceeding the length limit.
|
|
|
|
func (e *Event) Bytes() []byte {
|
|
|
|
buffer := new(bytes.Buffer)
|
|
|
|
|
2017-01-03 16:02:24 +00:00
|
|
|
// Tags.
|
|
|
|
if e.Tags != nil {
|
|
|
|
e.Tags.writeTo(buffer)
|
|
|
|
}
|
|
|
|
|
2016-11-23 00:32:14 +00:00
|
|
|
// Event prefix.
|
2016-11-19 14:36:17 +00:00
|
|
|
if e.Source != nil {
|
2017-01-03 16:02:24 +00:00
|
|
|
buffer.WriteByte(messagePrefix)
|
2016-11-19 14:36:17 +00:00
|
|
|
e.Source.writeTo(buffer)
|
2017-01-03 16:02:24 +00:00
|
|
|
buffer.WriteByte(eventSpace)
|
2016-11-13 08:30:43 +00:00
|
|
|
}
|
|
|
|
|
2016-11-23 00:32:14 +00:00
|
|
|
// Command is required.
|
2016-11-13 08:30:43 +00:00
|
|
|
buffer.WriteString(e.Command)
|
|
|
|
|
2016-11-23 00:32:14 +00:00
|
|
|
// Space separated list of arguments.
|
2016-11-13 08:30:43 +00:00
|
|
|
if len(e.Params) > 0 {
|
2017-01-03 16:02:24 +00:00
|
|
|
buffer.WriteByte(eventSpace)
|
|
|
|
buffer.WriteString(strings.Join(e.Params, string(eventSpace)))
|
2016-11-13 08:30:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(e.Trailing) > 0 || e.EmptyTrailing {
|
2017-01-03 16:02:24 +00:00
|
|
|
buffer.WriteByte(eventSpace)
|
|
|
|
buffer.WriteByte(messagePrefix)
|
2016-11-13 08:30:43 +00:00
|
|
|
buffer.WriteString(e.Trailing)
|
|
|
|
}
|
|
|
|
|
2016-11-23 00:32:14 +00:00
|
|
|
// We need the limit the buffer length.
|
2016-11-13 08:30:43 +00:00
|
|
|
if buffer.Len() > (maxLength) {
|
|
|
|
buffer.Truncate(maxLength)
|
|
|
|
}
|
|
|
|
|
2016-11-23 00:32:14 +00:00
|
|
|
out := buffer.Bytes()
|
|
|
|
|
|
|
|
// Strip newlines and carriage returns.
|
|
|
|
for i := 0; i < len(out); i++ {
|
|
|
|
if out[i] == 0x0A || out[i] == 0x0D {
|
|
|
|
out = append(out[:i], out[i+1:]...)
|
|
|
|
i-- // Decrease the index so we can pick up where we left off.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return out
|
2016-11-13 08:30:43 +00:00
|
|
|
}
|
|
|
|
|
2016-11-23 00:32:14 +00:00
|
|
|
// Raw returns a string representation of this event. Strips all newlines
|
|
|
|
// and carriage returns.
|
2016-11-19 15:55:19 +00:00
|
|
|
func (e *Event) Raw() string {
|
|
|
|
return string(e.Bytes())
|
|
|
|
}
|
|
|
|
|
2016-11-23 00:32:14 +00:00
|
|
|
// String returns a prettified string representation of this event. Strips
|
|
|
|
// all newlines and carriage returns.
|
2016-11-19 15:55:19 +00:00
|
|
|
//
|
2016-11-23 00:32:14 +00:00
|
|
|
// Per RFC2812 section 2.3, messages should not exceed 512 characters in
|
|
|
|
// length. This method forces that limit by discarding any characters
|
2016-11-19 15:55:19 +00:00
|
|
|
// exceeding the length limit.
|
|
|
|
func (e *Event) String() (out string) {
|
2016-11-23 00:32:14 +00:00
|
|
|
// Event prefix.
|
2016-11-19 15:55:19 +00:00
|
|
|
if e.Source != nil {
|
|
|
|
if e.Source.Name != "" {
|
|
|
|
out += fmt.Sprintf("[%s] ", e.Source.Name)
|
|
|
|
} else {
|
|
|
|
out += fmt.Sprintf("[%s] ", e.Source)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-23 00:32:14 +00:00
|
|
|
// Command is required.
|
2016-11-19 15:55:19 +00:00
|
|
|
out += e.Command
|
|
|
|
|
2016-11-23 00:32:14 +00:00
|
|
|
// Space separated list of arguments.
|
2016-11-19 15:55:19 +00:00
|
|
|
if len(e.Params) > 0 {
|
2017-01-03 16:02:24 +00:00
|
|
|
out += " " + strings.Join(e.Params, string(eventSpace))
|
2016-11-19 15:55:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(e.Trailing) > 0 || e.EmptyTrailing {
|
|
|
|
out += " :" + e.Trailing
|
|
|
|
}
|
|
|
|
|
2016-11-23 00:32:14 +00:00
|
|
|
// We need the limit the buffer length.
|
2016-11-19 15:55:19 +00:00
|
|
|
if len(out) > (maxLength) {
|
|
|
|
out = out[0 : maxLength-1]
|
|
|
|
}
|
|
|
|
|
2016-11-23 00:32:14 +00:00
|
|
|
// Strip newlines and carriage returns.
|
|
|
|
for i := 0; i < len(out); i++ {
|
|
|
|
if out[i] == 0x0A || out[i] == 0x0D {
|
|
|
|
out = out[:i] + out[i+1:]
|
|
|
|
i-- // Decrease the index so we can pick up where we left off.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-19 15:55:19 +00:00
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
2016-11-23 00:32:14 +00:00
|
|
|
// IsAction checks to see if the event is a PRIVMSG, and is an ACTION (/me).
|
2016-11-13 14:10:34 +00:00
|
|
|
func (e *Event) IsAction() bool {
|
2016-11-17 19:55:09 +00:00
|
|
|
if len(e.Trailing) <= 0 || e.Command != PRIVMSG {
|
2016-11-13 14:10:34 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2016-12-24 05:15:41 +00:00
|
|
|
if !strings.HasPrefix(e.Trailing, "\001ACTION") || e.Trailing[len(e.Trailing)-1] != ctcpDelim {
|
2016-11-13 14:10:34 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2016-12-23 23:35:04 +00:00
|
|
|
// IsFromChannel checks to see if a message was from a channel (rather than
|
|
|
|
// a private message).
|
|
|
|
func (e *Event) IsFromChannel() bool {
|
2016-12-24 01:01:40 +00:00
|
|
|
if len(e.Params) != 1 {
|
2016-12-23 23:35:04 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2016-12-24 01:01:40 +00:00
|
|
|
if e.Command != "PRIVMSG" || !IsValidChannel(e.Params[0]) {
|
2016-12-23 23:35:04 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2016-12-24 01:01:40 +00:00
|
|
|
return true
|
2016-12-23 23:35:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// IsFromUser checks to see if a message was from a user (rather than a
|
|
|
|
// channel).
|
|
|
|
func (e *Event) IsFromUser() bool {
|
2016-12-24 01:01:40 +00:00
|
|
|
if len(e.Params) != 1 {
|
2016-12-23 23:35:04 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2016-12-24 01:01:40 +00:00
|
|
|
if e.Command != "PRIVMSG" || !IsValidNick(e.Params[0]) {
|
2016-12-23 23:35:04 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2016-12-24 01:01:40 +00:00
|
|
|
return true
|
2016-12-23 23:35:04 +00:00
|
|
|
}
|
|
|
|
|
2016-11-23 00:32:14 +00:00
|
|
|
// StripAction strips the action encoding from a PRIVMSG ACTION (/me).
|
2016-11-13 14:10:34 +00:00
|
|
|
func (e *Event) StripAction() string {
|
|
|
|
if !e.IsAction() || len(e.Trailing) < 9 {
|
|
|
|
return e.Trailing
|
|
|
|
}
|
|
|
|
|
|
|
|
return e.Trailing[8 : len(e.Trailing)-1]
|
|
|
|
}
|
2016-11-23 02:58:26 +00:00
|
|
|
|
|
|
|
// EventLimiter is a custom ticker which lets you rate limit sending events
|
|
|
|
// to a function (e.g. Client.Send()), with optional burst support. See
|
|
|
|
// NewEventLimiter() for more information.
|
|
|
|
type EventLimiter struct {
|
|
|
|
tick *time.Ticker
|
|
|
|
throttle chan time.Time
|
|
|
|
fn func(*Event) error
|
|
|
|
}
|
|
|
|
|
|
|
|
// loop is used to read events from the internal time.Ticker.
|
|
|
|
func (el *EventLimiter) loop() {
|
|
|
|
// This should exit itself once el.Stop() is called.
|
|
|
|
for t := range el.tick.C {
|
|
|
|
select {
|
|
|
|
case el.throttle <- t:
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop closes the ticker, and prevents re-use of the EventLimiter. Use this
|
|
|
|
// to prevent EventLimiter from keeping unnecessary pointers in memory.
|
|
|
|
func (el *EventLimiter) Stop() {
|
|
|
|
el.tick.Stop()
|
|
|
|
el.fn = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send is the subtitute function used to send the event the the previously
|
|
|
|
// specified send function.
|
|
|
|
//
|
|
|
|
// This WILL panic if Stop() was already called on the EventLimiter.
|
|
|
|
func (el *EventLimiter) Send(event *Event) error {
|
|
|
|
// Ensure nobody is sending to it once it's closed.
|
|
|
|
if el.fn == nil {
|
|
|
|
panic("attempted send on closed EventLimiter")
|
|
|
|
}
|
|
|
|
|
|
|
|
<-el.throttle
|
|
|
|
return el.fn(event)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SendAll sends a list of events to Send(). SendAll will return the first
|
|
|
|
// error it gets when attempting to Send() to the predefined Send function.
|
|
|
|
// It will not attempt to continue processing the list of events.
|
|
|
|
func (el *EventLimiter) SendAll(events ...*Event) error {
|
|
|
|
for i := 0; i < len(events); i++ {
|
|
|
|
if err := el.Send(events[i]); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewEventLimiter returns a NewEventLimiter which can be used to rate limit
|
|
|
|
// events being sent to a Send function. This does support bursting a
|
|
|
|
// certain amount of messages if there are less than burstCount.
|
|
|
|
//
|
|
|
|
// Ensure that Stop() is called on the returned EventLimiter, otherwise
|
|
|
|
// the limiter may keep unwanted pointers to data in memory.
|
|
|
|
func NewEventLimiter(burstCount int, rate time.Duration, eventFunc func(event *Event) error) *EventLimiter {
|
|
|
|
limiter := &EventLimiter{
|
|
|
|
tick: time.NewTicker(rate),
|
|
|
|
throttle: make(chan time.Time, burstCount),
|
|
|
|
fn: eventFunc,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Push the ticket into the background. If you want to stop this, simply
|
|
|
|
// use EventLimiter.Stop().
|
|
|
|
go limiter.loop()
|
|
|
|
|
|
|
|
return limiter
|
|
|
|
}
|