Update dependencies and remove old matterclient lib (#2067)
This commit is contained in:
5
vendor/github.com/lrstanley/girc/.editorconfig
generated
vendored
5
vendor/github.com/lrstanley/girc/.editorconfig
generated
vendored
@@ -1,6 +1,9 @@
|
||||
# THIS FILE IS GENERATED! DO NOT EDIT! Maintained by Terraform.
|
||||
#
|
||||
# editorconfig.org
|
||||
# editorconfig: https://editorconfig.org/
|
||||
# actual source: https://github.com/lrstanley/.github/blob/master/terraform/github-common-files/templates/.editorconfig
|
||||
#
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
|
||||
191
vendor/github.com/lrstanley/girc/.golangci.yml
generated
vendored
191
vendor/github.com/lrstanley/girc/.golangci.yml
generated
vendored
@@ -1,11 +1,33 @@
|
||||
# THIS FILE IS GENERATED! DO NOT EDIT! Maintained by Terraform.
|
||||
#
|
||||
# golangci-lint: https://golangci-lint.run/
|
||||
# false-positives: https://golangci-lint.run/usage/false-positives/
|
||||
# actual source: https://github.com/lrstanley/.github/blob/master/terraform/github-common-files/templates/.golangci.yml
|
||||
# modified variant of: https://gist.github.com/maratori/47a4d00457a92aa426dbd48a18776322
|
||||
#
|
||||
|
||||
run:
|
||||
tests: False
|
||||
timeout: 3m
|
||||
|
||||
issues:
|
||||
max-per-linter: 0
|
||||
max-same-issues: 0
|
||||
# max-same-issues: 0
|
||||
max-same-issues: 50
|
||||
|
||||
exclude-rules:
|
||||
- source: "(noinspection|TODO)"
|
||||
linters: [godot]
|
||||
- source: "//noinspection"
|
||||
linters: [gocritic]
|
||||
- path: "_test\\.go"
|
||||
linters:
|
||||
- bodyclose
|
||||
- dupl
|
||||
- funlen
|
||||
- goconst
|
||||
- gosec
|
||||
- noctx
|
||||
- wrapcheck
|
||||
|
||||
severity:
|
||||
default-severity: error
|
||||
@@ -16,17 +38,102 @@ severity:
|
||||
severity: warning
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- asciicheck
|
||||
- exportloopref
|
||||
- gci
|
||||
- gocritic
|
||||
- gofmt
|
||||
- misspell
|
||||
- asasalint # checks for pass []any as any in variadic func(...any)
|
||||
- asciicheck # checks that your code does not contain non-ASCII identifiers
|
||||
- bidichk # checks for dangerous unicode character sequences
|
||||
- bodyclose # checks whether HTTP response body is closed successfully
|
||||
- cyclop # checks function and package cyclomatic complexity
|
||||
- dupl # tool for code clone detection
|
||||
- durationcheck # checks for two durations multiplied together
|
||||
- errcheck # checking for unchecked errors, these unchecked errors can be critical bugs in some cases
|
||||
- errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13
|
||||
- execinquery # checks query string in Query function which reads your Go src files and warning it finds
|
||||
- exportloopref # checks for pointers to enclosing loop variables
|
||||
- forbidigo # forbids identifiers
|
||||
- funlen # tool for detection of long functions
|
||||
- gci # controls golang package import order and makes it always deterministic
|
||||
- gocheckcompilerdirectives # validates go compiler directive comments (//go:)
|
||||
- gochecknoinits # checks that no init functions are present in Go code
|
||||
- goconst # finds repeated strings that could be replaced by a constant
|
||||
- gocritic # provides diagnostics that check for bugs, performance and style issues
|
||||
- gocyclo # computes and checks the cyclomatic complexity of functions
|
||||
- godot # checks if comments end in a period
|
||||
- godox # detects FIXME, TODO and other comment keywords
|
||||
- goimports # in addition to fixing imports, goimports also formats your code in the same style as gofmt
|
||||
- gomnd # detects magic numbers
|
||||
- gomoddirectives # manages the use of 'replace', 'retract', and 'excludes' directives in go.mod
|
||||
- gomodguard # allow and block lists linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations
|
||||
- goprintffuncname # checks that printf-like functions are named with f at the end
|
||||
- gosec # inspects source code for security problems
|
||||
- gosimple # specializes in simplifying a code
|
||||
- govet # reports suspicious constructs, such as Printf calls whose arguments do not align with the format string
|
||||
- ineffassign # detects when assignments to existing variables are not used
|
||||
- loggercheck # checks key value pairs for common logger libraries (kitlog,klog,logr,zap)
|
||||
- makezero # finds slice declarations with non-zero initial length
|
||||
- misspell # finds commonly misspelled words
|
||||
- musttag # enforces field tags in (un)marshaled structs
|
||||
- nakedret # finds naked returns in functions greater than a specified function length
|
||||
- nilerr # finds the code that returns nil even if it checks that the error is not nil
|
||||
- nilnil # checks that there is no simultaneous return of nil error and an invalid value
|
||||
- noctx # finds sending http request without context.Context
|
||||
- nosprintfhostport # checks for misuse of Sprintf to construct a host with port in a URL
|
||||
- predeclared # finds code that shadows one of Go's predeclared identifiers
|
||||
- promlinter # checks Prometheus metrics naming via promlint
|
||||
- reassign # checks that package variables are not reassigned
|
||||
- revive # fast, configurable, extensible, flexible, and beautiful linter for Go, drop-in replacement of golint
|
||||
- rowserrcheck # checks whether Err of rows is checked successfully
|
||||
- sqlclosecheck # checks that sql.Rows and sql.Stmt are closed
|
||||
- staticcheck # is a go vet on steroids, applying a ton of static analysis checks
|
||||
- stylecheck # is a replacement for golint
|
||||
- tenv # detects using os.Setenv instead of t.Setenv since Go1.17
|
||||
- testableexamples # checks if examples are testable (have an expected output)
|
||||
- tparallel # detects inappropriate usage of t.Parallel() method in your Go test codes
|
||||
- typecheck # like the front-end of a Go compiler, parses and type-checks Go code
|
||||
- unconvert # removes unnecessary type conversions
|
||||
- unparam # reports unused function parameters
|
||||
- unused # checks for unused constants, variables, functions and types
|
||||
- usestdlibvars # detects the possibility to use variables/constants from the Go standard library
|
||||
- wastedassign # finds wasted assignment statements
|
||||
- whitespace # detects leading and trailing whitespace
|
||||
|
||||
# disabled for now:
|
||||
# - errname # checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error
|
||||
# - gochecknoglobals # checks that no global variables exist
|
||||
# - gocognit # computes and checks the cognitive complexity of functions
|
||||
# - nestif # reports deeply nested if statements
|
||||
# - nonamedreturns # reports all named returns
|
||||
# - testpackage # makes you use a separate _test package
|
||||
|
||||
linters-settings:
|
||||
cyclop:
|
||||
# The maximal code complexity to report.
|
||||
max-complexity: 30
|
||||
# The maximal average package complexity.
|
||||
# If it's higher than 0.0 (float) the check is enabled
|
||||
package-average: 10.0
|
||||
|
||||
errcheck:
|
||||
# Report about not checking of errors in type assertions: `a := b.(MyStruct)`.
|
||||
# Such cases aren't reported by default.
|
||||
check-type-assertions: true
|
||||
|
||||
funlen:
|
||||
# Checks the number of lines in a function.
|
||||
# If lower than 0, disable the check.
|
||||
lines: 150
|
||||
# Checks the number of statements in a function.
|
||||
# If lower than 0, disable the check.
|
||||
statements: 75
|
||||
|
||||
# gocognit:
|
||||
# # Minimal code complexity to report.
|
||||
# min-complexity: 25
|
||||
|
||||
gocritic:
|
||||
disabled-checks:
|
||||
- whyNoLint
|
||||
- hugeParam
|
||||
- ifElseChain
|
||||
enabled-tags:
|
||||
@@ -34,5 +141,71 @@ linters-settings:
|
||||
- opinionated
|
||||
- performance
|
||||
- style
|
||||
# https://go-critic.github.io/overview.
|
||||
settings:
|
||||
captLocal:
|
||||
# Whether to restrict checker to params only.
|
||||
paramsOnly: false
|
||||
underef:
|
||||
# Whether to skip (*x).method() calls where x is a pointer receiver.
|
||||
skipRecvDeref: false
|
||||
|
||||
gomnd:
|
||||
# Values always ignored: `time.Date`,
|
||||
# `strconv.FormatInt`, `strconv.FormatUint`, `strconv.FormatFloat`,
|
||||
# `strconv.ParseInt`, `strconv.ParseUint`, `strconv.ParseFloat`.
|
||||
ignored-functions:
|
||||
- os.Chmod
|
||||
- os.Mkdir
|
||||
- os.MkdirAll
|
||||
- os.OpenFile
|
||||
- os.WriteFile
|
||||
- prometheus.ExponentialBuckets
|
||||
- prometheus.ExponentialBucketsRange
|
||||
- prometheus.LinearBuckets
|
||||
|
||||
gomodguard:
|
||||
blocked:
|
||||
# List of blocked modules.
|
||||
modules:
|
||||
- github.com/golang/protobuf:
|
||||
recommendations:
|
||||
- google.golang.org/protobuf
|
||||
reason: "see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules"
|
||||
- github.com/satori/go.uuid:
|
||||
recommendations:
|
||||
- github.com/google/uuid
|
||||
reason: "satori's package is not maintained"
|
||||
- github.com/gofrs/uuid:
|
||||
recommendations:
|
||||
- github.com/google/uuid
|
||||
reason: "gofrs' package is not go module"
|
||||
|
||||
govet:
|
||||
check-shadowing: true
|
||||
enable-all: true
|
||||
# Run `go tool vet help` to see all analyzers.
|
||||
disable:
|
||||
- fieldalignment # too strict
|
||||
settings:
|
||||
shadow:
|
||||
# Whether to be strict about shadowing; can be noisy.
|
||||
strict: true
|
||||
|
||||
nakedret:
|
||||
# Make an issue if func has more lines of code than this setting, and it has naked returns.
|
||||
max-func-lines: 0
|
||||
|
||||
rowserrcheck:
|
||||
# database/sql is always checked
|
||||
packages:
|
||||
- github.com/jmoiron/sqlx
|
||||
|
||||
stylecheck:
|
||||
checks:
|
||||
- all
|
||||
- -ST1008 # handled by revive already.
|
||||
|
||||
tenv:
|
||||
# The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures.
|
||||
# Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked.
|
||||
all: true
|
||||
|
||||
42
vendor/github.com/lrstanley/girc/builtin.go
generated
vendored
42
vendor/github.com/lrstanley/girc/builtin.go
generated
vendored
@@ -408,6 +408,48 @@ func handleISUPPORT(c *Client, e Event) {
|
||||
c.state.serverOptions[name] = val
|
||||
}
|
||||
c.state.Unlock()
|
||||
|
||||
// Check for max line/nick/user/host lengths here.
|
||||
c.state.RLock()
|
||||
maxLineLength := c.state.maxLineLength
|
||||
c.state.RUnlock()
|
||||
maxNickLength := defaultNickLength
|
||||
maxUserLength := defaultUserLength
|
||||
maxHostLength := defaultHostLength
|
||||
|
||||
var ok bool
|
||||
var tmp int
|
||||
|
||||
if tmp, ok = c.GetServerOptionInt("LINELEN"); ok {
|
||||
maxLineLength = tmp
|
||||
c.state.Lock()
|
||||
c.state.maxLineLength = maxTagLength - 2 // -2 for CR-LF.
|
||||
c.state.Unlock()
|
||||
}
|
||||
|
||||
if tmp, ok = c.GetServerOptionInt("NICKLEN"); ok {
|
||||
maxNickLength = tmp
|
||||
}
|
||||
if tmp, ok = c.GetServerOptionInt("MAXNICKLEN"); ok && tmp > maxNickLength {
|
||||
maxNickLength = tmp
|
||||
}
|
||||
if tmp, ok = c.GetServerOptionInt("USERLEN"); ok && tmp > maxUserLength {
|
||||
maxUserLength = tmp
|
||||
}
|
||||
if tmp, ok = c.GetServerOptionInt("HOSTLEN"); ok && tmp > maxHostLength {
|
||||
maxHostLength = tmp
|
||||
}
|
||||
|
||||
prefixLen := defaultPrefixPadding + maxNickLength + maxUserLength + maxHostLength
|
||||
if prefixLen >= maxLineLength {
|
||||
// Give up and go with defaults.
|
||||
c.state.notify(c, UPDATE_GENERAL)
|
||||
return
|
||||
}
|
||||
c.state.Lock()
|
||||
c.state.maxPrefixLength = prefixLen
|
||||
c.state.Unlock()
|
||||
|
||||
c.state.notify(c, UPDATE_GENERAL)
|
||||
}
|
||||
|
||||
|
||||
4
vendor/github.com/lrstanley/girc/cap.go
generated
vendored
4
vendor/github.com/lrstanley/girc/cap.go
generated
vendored
@@ -267,9 +267,9 @@ func handleCAP(c *Client, e Event) {
|
||||
}
|
||||
|
||||
if isError {
|
||||
c.rx <- &Event{Command: ERROR, Params: []string{
|
||||
c.receive(&Event{Command: ERROR, Params: []string{
|
||||
fmt.Sprintf("closing connection: strict transport policy provided by server is invalid; possible MITM? config: %#v", sts),
|
||||
}}
|
||||
}})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
6
vendor/github.com/lrstanley/girc/cap_sasl.go
generated
vendored
6
vendor/github.com/lrstanley/girc/cap_sasl.go
generated
vendored
@@ -95,9 +95,9 @@ func handleSASL(c *Client, e Event) {
|
||||
// some reason. The SASL spec and IRCv3 spec do not define a clear
|
||||
// way to abort a SASL exchange, other than to disconnect, or proceed
|
||||
// with CAP END.
|
||||
c.rx <- &Event{Command: ERROR, Params: []string{
|
||||
c.receive(&Event{Command: ERROR, Params: []string{
|
||||
fmt.Sprintf("closing connection: SASL %s failed: %s", c.Config.SASL.Method(), e.Last()),
|
||||
}}
|
||||
}})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -131,5 +131,5 @@ func handleSASLError(c *Client, e Event) {
|
||||
// Authentication failed. The SASL spec and IRCv3 spec do not define a
|
||||
// clear way to abort a SASL exchange, other than to disconnect, or
|
||||
// proceed with CAP END.
|
||||
c.rx <- &Event{Command: ERROR, Params: []string{"closing connection: " + e.Last()}}
|
||||
c.receive(&Event{Command: ERROR, Params: []string{"closing connection: " + e.Last()}})
|
||||
}
|
||||
|
||||
7
vendor/github.com/lrstanley/girc/cap_tags.go
generated
vendored
7
vendor/github.com/lrstanley/girc/cap_tags.go
generated
vendored
@@ -52,9 +52,12 @@ type Tags map[string]string
|
||||
|
||||
// ParseTags parses out the key-value map of tags. raw should only be the tag
|
||||
// data, not a full message. For example:
|
||||
// @aaa=bbb;ccc;example.com/ddd=eee
|
||||
//
|
||||
// @aaa=bbb;ccc;example.com/ddd=eee
|
||||
//
|
||||
// NOT:
|
||||
// @aaa=bbb;ccc;example.com/ddd=eee :nick!ident@host.com PRIVMSG me :Hello
|
||||
//
|
||||
// @aaa=bbb;ccc;example.com/ddd=eee :nick!ident@host.com PRIVMSG me :Hello
|
||||
//
|
||||
// Technically, there is a length limit of 4096, but the server should reject
|
||||
// tag messages longer than this.
|
||||
|
||||
89
vendor/github.com/lrstanley/girc/client.go
generated
vendored
89
vendor/github.com/lrstanley/girc/client.go
generated
vendored
@@ -155,6 +155,10 @@ type Config struct {
|
||||
// and the client. If this is set to -1, the client will not attempt to
|
||||
// send client -> server PING requests.
|
||||
PingDelay time.Duration
|
||||
// PingTimeout specifies the duration at which girc will assume
|
||||
// that the connection to the server has been lost if no PONG
|
||||
// message has been received in reply to an outstanding PING.
|
||||
PingTimeout time.Duration
|
||||
|
||||
// disableTracking disables all channel and user-level tracking. Useful
|
||||
// for highly embedded scripts with single purposes. This has an exported
|
||||
@@ -179,13 +183,13 @@ type Config struct {
|
||||
// server.
|
||||
//
|
||||
// Client expectations:
|
||||
// - Perform any proxy resolution.
|
||||
// - Check the reverse DNS and forward DNS match.
|
||||
// - Check the IP against suitable access controls (ipaccess, dnsbl, etc).
|
||||
// - Perform any proxy resolution.
|
||||
// - Check the reverse DNS and forward DNS match.
|
||||
// - Check the IP against suitable access controls (ipaccess, dnsbl, etc).
|
||||
//
|
||||
// More information:
|
||||
// - https://ircv3.net/specs/extensions/webirc.html
|
||||
// - https://kiwiirc.com/docs/webirc
|
||||
// - https://ircv3.net/specs/extensions/webirc.html
|
||||
// - https://kiwiirc.com/docs/webirc
|
||||
type WebIRC struct {
|
||||
// Password that authenticates the WEBIRC command from this client.
|
||||
Password string
|
||||
@@ -262,6 +266,10 @@ func New(config Config) *Client {
|
||||
c.Config.PingDelay = 600 * time.Second
|
||||
}
|
||||
|
||||
if c.Config.PingTimeout == 0 {
|
||||
c.Config.PingTimeout = 60 * time.Second
|
||||
}
|
||||
|
||||
envDebug, _ := strconv.ParseBool(os.Getenv("GIRC_DEBUG"))
|
||||
if c.Config.Debug == nil {
|
||||
if envDebug {
|
||||
@@ -300,6 +308,23 @@ func New(config Config) *Client {
|
||||
return c
|
||||
}
|
||||
|
||||
// receive is a wrapper for sending to the Client.rx channel. It will timeout if
|
||||
// the event can't be sent within 30s.
|
||||
func (c *Client) receive(e *Event) {
|
||||
t := time.NewTimer(30 * time.Second)
|
||||
defer func() {
|
||||
if !t.Stop() {
|
||||
<-t.C
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case c.rx <- e:
|
||||
case <-t.C:
|
||||
c.debugLogEvent(e, true)
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a brief description of the current client state.
|
||||
func (c *Client) String() string {
|
||||
connected := c.IsConnected()
|
||||
@@ -380,7 +405,7 @@ func (e *ErrEvent) Error() string {
|
||||
return e.Event.Last()
|
||||
}
|
||||
|
||||
func (c *Client) execLoop(ctx context.Context, errs chan error, wg *sync.WaitGroup) {
|
||||
func (c *Client) execLoop(ctx context.Context) error {
|
||||
c.debug.Print("starting execLoop")
|
||||
defer c.debug.Print("closing execLoop")
|
||||
|
||||
@@ -403,9 +428,10 @@ func (c *Client) execLoop(ctx context.Context, errs chan error, wg *sync.WaitGro
|
||||
}
|
||||
|
||||
done:
|
||||
wg.Done()
|
||||
return
|
||||
return nil
|
||||
case event = <-c.rx:
|
||||
c.RunHandlers(event)
|
||||
|
||||
if event != nil && event.Command == ERROR {
|
||||
// Handles incoming ERROR responses. These are only ever sent
|
||||
// by the server (with the exception that this library may use
|
||||
@@ -415,13 +441,9 @@ func (c *Client) execLoop(ctx context.Context, errs chan error, wg *sync.WaitGro
|
||||
// some reason the server doesn't disconnect the client, or
|
||||
// if this library is the source of the error, this should
|
||||
// signal back up to the main connect loop, to disconnect.
|
||||
errs <- &ErrEvent{Event: event}
|
||||
|
||||
// Make sure to not actually exit, so we can let any handlers
|
||||
// actually handle the ERROR event.
|
||||
return &ErrEvent{Event: event}
|
||||
}
|
||||
|
||||
c.RunHandlers(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -669,8 +691,7 @@ func (c *Client) IsInChannel(channel string) (in bool) {
|
||||
// during client connection. This is also known as ISUPPORT (or RPL_PROTOCTL).
|
||||
// Will panic if used when tracking has been disabled. Examples of usage:
|
||||
//
|
||||
// nickLen, success := GetServerOption("MAXNICKLEN")
|
||||
//
|
||||
// nickLen, success := GetServerOption("MAXNICKLEN")
|
||||
func (c *Client) GetServerOption(key string) (result string, ok bool) {
|
||||
c.panicIfNotTracking()
|
||||
|
||||
@@ -680,6 +701,42 @@ func (c *Client) GetServerOption(key string) (result string, ok bool) {
|
||||
return result, ok
|
||||
}
|
||||
|
||||
// GetServerOptionInt retrieves a server capability setting (as an integer) that was
|
||||
// retrieved during client connection. This is also known as ISUPPORT (or RPL_PROTOCTL).
|
||||
// Will panic if used when tracking has been disabled. Examples of usage:
|
||||
//
|
||||
// nickLen, success := GetServerOption("MAXNICKLEN")
|
||||
func (c *Client) GetServerOptionInt(key string) (result int, ok bool) {
|
||||
var data string
|
||||
var err error
|
||||
|
||||
data, ok = c.GetServerOption(key)
|
||||
if !ok {
|
||||
return result, ok
|
||||
}
|
||||
result, err = strconv.Atoi(data)
|
||||
if err != nil {
|
||||
ok = false
|
||||
}
|
||||
|
||||
return result, ok
|
||||
}
|
||||
|
||||
// MaxEventLength returns the maximum supported server length of an event. This is the
|
||||
// maximum length of the command and arguments, excluding the source/prefix supported
|
||||
// by the protocol. If state tracking is enabled, this will utilize ISUPPORT/IRCv3
|
||||
// information to more accurately calculate the maximum supported length (i.e. extended
|
||||
// length events).
|
||||
func (c *Client) MaxEventLength() (max int) {
|
||||
if !c.Config.disableTracking {
|
||||
c.state.RLock()
|
||||
max = c.state.maxLineLength - c.state.maxPrefixLength
|
||||
c.state.RUnlock()
|
||||
return max
|
||||
}
|
||||
return DefaultMaxLineLength - DefaultMaxPrefixLength
|
||||
}
|
||||
|
||||
// NetworkName returns the network identifier. E.g. "EsperNet", "ByteIRC".
|
||||
// May be empty if the server does not support RPL_ISUPPORT (or RPL_PROTOCTL).
|
||||
// Will panic if used when tracking has been disabled.
|
||||
@@ -773,7 +830,7 @@ func (c *Client) debugLogEvent(e *Event, dropped bool) {
|
||||
var prefix string
|
||||
|
||||
if dropped {
|
||||
prefix = "dropping event (disconnected):"
|
||||
prefix = "dropping event (disconnected or timeout):"
|
||||
} else {
|
||||
prefix = ">"
|
||||
}
|
||||
|
||||
8
vendor/github.com/lrstanley/girc/commands.go
generated
vendored
8
vendor/github.com/lrstanley/girc/commands.go
generated
vendored
@@ -25,8 +25,8 @@ func (cmd *Commands) Nick(name string) {
|
||||
// prevent sending extensive JOIN commands.
|
||||
func (cmd *Commands) Join(channels ...string) {
|
||||
// We can join multiple channels at once, however we need to ensure that
|
||||
// we are not exceeding the line length. (see maxLength)
|
||||
max := maxLength - len(JOIN) - 1
|
||||
// we are not exceeding the line length (see Client.MaxEventLength()).
|
||||
max := cmd.c.MaxEventLength() - len(JOIN) - 1
|
||||
|
||||
var buffer string
|
||||
|
||||
@@ -329,8 +329,8 @@ func (cmd *Commands) List(channels ...string) {
|
||||
}
|
||||
|
||||
// We can LIST multiple channels at once, however we need to ensure that
|
||||
// we are not exceeding the line length. (see maxLength)
|
||||
max := maxLength - len(JOIN) - 1
|
||||
// we are not exceeding the line length (see Client.MaxEventLength()).
|
||||
max := cmd.c.MaxEventLength() - len(JOIN) - 1
|
||||
|
||||
var buffer string
|
||||
|
||||
|
||||
208
vendor/github.com/lrstanley/girc/conn.go
generated
vendored
208
vendor/github.com/lrstanley/girc/conn.go
generated
vendored
@@ -12,6 +12,8 @@ import (
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/lrstanley/girc/internal/ctxgroup"
|
||||
)
|
||||
|
||||
// Messages are delimited with CR and LF line endings, we're using the last
|
||||
@@ -142,17 +144,44 @@ type ErrParseEvent struct {
|
||||
|
||||
func (e ErrParseEvent) Error() string { return "unable to parse event: " + e.Line }
|
||||
|
||||
func (c *ircConn) decode() (event *Event, err error) {
|
||||
line, err := c.io.ReadString(delim)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
type decodedEvent struct {
|
||||
event *Event
|
||||
err error
|
||||
}
|
||||
|
||||
if event = ParseEvent(line); event == nil {
|
||||
return nil, ErrParseEvent{line}
|
||||
}
|
||||
func (c *ircConn) decode() <-chan decodedEvent {
|
||||
ch := make(chan decodedEvent)
|
||||
|
||||
return event, nil
|
||||
go func() {
|
||||
defer close(ch)
|
||||
|
||||
line, err := c.io.ReadString(delim)
|
||||
if err != nil {
|
||||
select {
|
||||
case ch <- decodedEvent{err: err}:
|
||||
default:
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
event := ParseEvent(line)
|
||||
if event == nil {
|
||||
select {
|
||||
case ch <- decodedEvent{err: ErrParseEvent{Line: line}}:
|
||||
default:
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case ch <- decodedEvent{event: event}:
|
||||
default:
|
||||
}
|
||||
}()
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
func (c *ircConn) encode(event *Event) error {
|
||||
@@ -291,20 +320,17 @@ startConn:
|
||||
} else {
|
||||
c.conn = newMockConn(mock)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
|
||||
var ctx context.Context
|
||||
ctx, c.stop = context.WithCancel(context.Background())
|
||||
c.mu.Unlock()
|
||||
|
||||
errs := make(chan error, 4)
|
||||
var wg sync.WaitGroup
|
||||
// 4 being the number of goroutines we need to finish when this function
|
||||
// returns.
|
||||
wg.Add(4)
|
||||
go c.execLoop(ctx, errs, &wg)
|
||||
go c.readLoop(ctx, errs, &wg)
|
||||
go c.sendLoop(ctx, errs, &wg)
|
||||
go c.pingLoop(ctx, errs, &wg)
|
||||
group := ctxgroup.New(ctx)
|
||||
|
||||
group.Go(c.execLoop)
|
||||
group.Go(c.readLoop)
|
||||
group.Go(c.sendLoop)
|
||||
group.Go(c.pingLoop)
|
||||
|
||||
// Passwords first.
|
||||
|
||||
@@ -338,16 +364,15 @@ startConn:
|
||||
c.RunHandlers(&Event{Command: INITIALIZED, Params: []string{addr}})
|
||||
|
||||
// Wait for the first error.
|
||||
var result error
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
err := group.Wait()
|
||||
if err != nil {
|
||||
c.debug.Printf("received error, beginning cleanup: %v", err)
|
||||
} else {
|
||||
if !c.state.sts.beginUpgrade {
|
||||
c.debug.Print("received request to close, beginning clean up")
|
||||
}
|
||||
|
||||
c.RunHandlers(&Event{Command: CLOSED, Params: []string{addr}})
|
||||
case err := <-errs:
|
||||
c.debug.Printf("received error, beginning cleanup: %v", err)
|
||||
result = err
|
||||
}
|
||||
|
||||
// Make sure that the connection is closed if not already.
|
||||
@@ -363,20 +388,13 @@ startConn:
|
||||
|
||||
c.RunHandlers(&Event{Command: DISCONNECTED, Params: []string{addr}})
|
||||
|
||||
// Once we have our error/result, let all other functions know we're done.
|
||||
c.debug.Print("waiting for all routines to finish")
|
||||
|
||||
// Wait for all goroutines to finish.
|
||||
wg.Wait()
|
||||
close(errs)
|
||||
|
||||
// This helps ensure that the end user isn't improperly using the client
|
||||
// more than once. If they want to do this, they should be using multiple
|
||||
// clients, not multiple instances of Connect().
|
||||
c.mu.Lock()
|
||||
c.conn = nil
|
||||
|
||||
if result == nil {
|
||||
if err == nil {
|
||||
if c.state.sts.beginUpgrade {
|
||||
c.state.sts.beginUpgrade = false
|
||||
c.mu.Unlock()
|
||||
@@ -389,76 +407,85 @@ startConn:
|
||||
}
|
||||
c.mu.Unlock()
|
||||
|
||||
return result
|
||||
return err
|
||||
}
|
||||
|
||||
// readLoop sets a timeout of 300 seconds, and then attempts to read from the
|
||||
// IRC server. If there is an error, it calls Reconnect.
|
||||
func (c *Client) readLoop(ctx context.Context, errs chan error, wg *sync.WaitGroup) {
|
||||
func (c *Client) readLoop(ctx context.Context) error {
|
||||
c.debug.Print("starting readLoop")
|
||||
defer c.debug.Print("closing readLoop")
|
||||
|
||||
var event *Event
|
||||
var err error
|
||||
var de decodedEvent
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
wg.Done()
|
||||
return
|
||||
return nil
|
||||
default:
|
||||
_ = c.conn.sock.SetReadDeadline(time.Now().Add(300 * time.Second))
|
||||
event, err = c.conn.decode()
|
||||
if err != nil {
|
||||
errs <- err
|
||||
wg.Done()
|
||||
return
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case de = <-c.conn.decode():
|
||||
}
|
||||
|
||||
if de.err != nil {
|
||||
return de.err
|
||||
}
|
||||
|
||||
// Check if it's an echo-message.
|
||||
if !c.Config.disableTracking {
|
||||
event.Echo = (event.Command == PRIVMSG || event.Command == NOTICE) &&
|
||||
event.Source != nil && event.Source.ID() == c.GetID()
|
||||
de.event.Echo = (de.event.Command == PRIVMSG || de.event.Command == NOTICE) &&
|
||||
de.event.Source != nil && de.event.Source.ID() == c.GetID()
|
||||
}
|
||||
|
||||
c.rx <- event
|
||||
c.receive(de.event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send sends an event to the server. Use Client.RunHandlers() if you are
|
||||
// simply looking to trigger handlers with an event.
|
||||
// Send sends an event to the server. Send will split events if the event is longer
|
||||
// than what the server supports, and is an event that supports splitting. Use
|
||||
// Client.RunHandlers() if you are simply looking to trigger handlers with an event.
|
||||
func (c *Client) Send(event *Event) {
|
||||
var delay time.Duration
|
||||
|
||||
if !c.Config.AllowFlood {
|
||||
c.mu.RLock()
|
||||
|
||||
// Drop the event early as we're disconnected, this way we don't have to wait
|
||||
// the (potentially long) rate limit delay before dropping.
|
||||
if c.conn == nil {
|
||||
c.debugLogEvent(event, true)
|
||||
c.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
c.conn.mu.Lock()
|
||||
delay = c.conn.rate(event.Len())
|
||||
c.conn.mu.Unlock()
|
||||
c.mu.RUnlock()
|
||||
}
|
||||
|
||||
if c.Config.GlobalFormat && len(event.Params) > 0 && event.Params[len(event.Params)-1] != "" &&
|
||||
(event.Command == PRIVMSG || event.Command == TOPIC || event.Command == NOTICE) {
|
||||
event.Params[len(event.Params)-1] = Fmt(event.Params[len(event.Params)-1])
|
||||
}
|
||||
|
||||
<-time.After(delay)
|
||||
c.write(event)
|
||||
var events []*Event
|
||||
events = event.split(c.MaxEventLength())
|
||||
|
||||
for _, e := range events {
|
||||
if !c.Config.AllowFlood {
|
||||
c.mu.RLock()
|
||||
|
||||
// Drop the event early as we're disconnected, this way we don't have to wait
|
||||
// the (potentially long) rate limit delay before dropping.
|
||||
if c.conn == nil {
|
||||
c.debugLogEvent(e, true)
|
||||
c.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
c.conn.mu.Lock()
|
||||
delay = c.conn.rate(e.Len())
|
||||
c.conn.mu.Unlock()
|
||||
c.mu.RUnlock()
|
||||
}
|
||||
|
||||
<-time.After(delay)
|
||||
c.write(e)
|
||||
}
|
||||
}
|
||||
|
||||
// write is the lower level function to write an event. It does not have a
|
||||
// write-delay when sending events.
|
||||
// write-delay when sending events. write will timeout after 30s if the event
|
||||
// can't be sent.
|
||||
func (c *Client) write(event *Event) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
@@ -468,7 +495,19 @@ func (c *Client) write(event *Event) {
|
||||
c.debugLogEvent(event, true)
|
||||
return
|
||||
}
|
||||
c.tx <- event
|
||||
|
||||
t := time.NewTimer(30 * time.Second)
|
||||
defer func() {
|
||||
if !t.Stop() {
|
||||
<-t.C
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case c.tx <- event:
|
||||
case <-t.C:
|
||||
c.debugLogEvent(event, true)
|
||||
}
|
||||
}
|
||||
|
||||
// rate allows limiting events based on how frequent the event is being sent,
|
||||
@@ -487,7 +526,7 @@ func (c *ircConn) rate(chars int) time.Duration {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *Client) sendLoop(ctx context.Context, errs chan error, wg *sync.WaitGroup) {
|
||||
func (c *Client) sendLoop(ctx context.Context) error {
|
||||
c.debug.Print("starting sendLoop")
|
||||
defer c.debug.Print("closing sendLoop")
|
||||
|
||||
@@ -537,18 +576,14 @@ func (c *Client) sendLoop(ctx context.Context, errs chan error, wg *sync.WaitGro
|
||||
|
||||
if event.Command == QUIT {
|
||||
c.Close()
|
||||
wg.Done()
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errs <- err
|
||||
wg.Done()
|
||||
return
|
||||
return err
|
||||
}
|
||||
case <-ctx.Done():
|
||||
wg.Done()
|
||||
return
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -568,11 +603,10 @@ type ErrTimedOut struct {
|
||||
|
||||
func (ErrTimedOut) Error() string { return "timed out waiting for a requested PING response" }
|
||||
|
||||
func (c *Client) pingLoop(ctx context.Context, errs chan error, wg *sync.WaitGroup) {
|
||||
func (c *Client) pingLoop(ctx context.Context) error {
|
||||
// Don't run the pingLoop if they want to disable it.
|
||||
if c.Config.PingDelay <= 0 {
|
||||
wg.Done()
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
c.debug.Print("starting pingLoop")
|
||||
@@ -604,9 +638,8 @@ func (c *Client) pingLoop(ctx context.Context, errs chan error, wg *sync.WaitGro
|
||||
}
|
||||
|
||||
c.conn.mu.RLock()
|
||||
if pingSent && time.Since(c.conn.lastPong) > c.Config.PingDelay+(60*time.Second) {
|
||||
// It's 60 seconds over what out ping delay is, connection
|
||||
// has probably dropped.
|
||||
if pingSent && time.Since(c.conn.lastPong) > c.Config.PingDelay+c.Config.PingTimeout {
|
||||
// PingTimeout exceeded, connection has probably dropped.
|
||||
err := ErrTimedOut{
|
||||
TimeSinceSuccess: time.Since(c.conn.lastPong),
|
||||
LastPong: c.conn.lastPong,
|
||||
@@ -615,9 +648,7 @@ func (c *Client) pingLoop(ctx context.Context, errs chan error, wg *sync.WaitGro
|
||||
}
|
||||
|
||||
c.conn.mu.RUnlock()
|
||||
errs <- err
|
||||
wg.Done()
|
||||
return
|
||||
return err
|
||||
}
|
||||
c.conn.mu.RUnlock()
|
||||
|
||||
@@ -628,8 +659,7 @@ func (c *Client) pingLoop(ctx context.Context, errs chan error, wg *sync.WaitGro
|
||||
c.Cmd.Ping(fmt.Sprintf("%d", time.Now().UnixNano()))
|
||||
pingSent = true
|
||||
case <-ctx.Done():
|
||||
wg.Done()
|
||||
return
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
146
vendor/github.com/lrstanley/girc/event.go
generated
vendored
146
vendor/github.com/lrstanley/girc/event.go
generated
vendored
@@ -13,7 +13,41 @@ import (
|
||||
|
||||
const (
|
||||
eventSpace byte = ' ' // Separator.
|
||||
maxLength int = 510 // Maximum length is 510 (2 for line endings).
|
||||
|
||||
// TODO: if state tracking is enabled, we SHOULD be able to use it's known length.
|
||||
|
||||
// Can be overridden by the NICKLEN (or MAXNICKLEN) ISUPPORT parameter. 30 or 31
|
||||
// are typical values for this parameter advertised by servers today.
|
||||
defaultNickLength = 30
|
||||
// The maximum length of <username> may be specified by the USERLEN RPL_ISUPPORT
|
||||
// parameter. If this length is advertised, the username MUST be silently truncated
|
||||
// to the given length before being used.
|
||||
defaultUserLength = 18
|
||||
// If a looked-up domain name is longer than this length (or overridden by the
|
||||
// HOSTLEN ISUPPORT parameter), the server SHOULD opt to use the IP address instead,
|
||||
// so that the hostname is underneath this length.
|
||||
defaultHostLength = 63
|
||||
|
||||
// defaultPrefixPadding defaults the estimated prefix padding length of a given
|
||||
// event. See also:
|
||||
// [ ":" ( servername / ( nickname [ [ "!" user ] "@" host ] ) ) SPACE ]
|
||||
defaultPrefixPadding = 4
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultMaxLineLength is the default maximum length for an event. 510 (+2 for line endings)
|
||||
// is used as a default as this is used by many older implementations.
|
||||
//
|
||||
// See also: RFC 2812
|
||||
// IRC messages are always lines of characters terminated with a CR-LF
|
||||
// (Carriage Return - Line Feed) pair, and these messages SHALL NOT
|
||||
// exceed 512 characters in length, counting all characters including
|
||||
// the trailing CR-LF.
|
||||
DefaultMaxLineLength = 510
|
||||
|
||||
// DefaultMaxPrefixLength defines the default max ":nickname!user@host " length
|
||||
// that's used to calculate line splitting.
|
||||
DefaultMaxPrefixLength = defaultPrefixPadding + defaultNickLength + defaultUserLength + defaultHostLength
|
||||
)
|
||||
|
||||
// cutCRFunc is used to trim CR characters from prefixes/messages.
|
||||
@@ -125,16 +159,16 @@ func ParseEvent(raw string) (e *Event) {
|
||||
|
||||
// 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
|
||||
// <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 {
|
||||
// Source is the origin of the event.
|
||||
Source *Source `json:"source"`
|
||||
@@ -223,11 +257,80 @@ func (e *Event) Equals(ev *Event) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Len calculates the length of the string representation of event. Note that
|
||||
// this will return the true length (even if longer than what IRC supports),
|
||||
// which may be useful if you are trying to check and see if a message is
|
||||
// too long, to trim it down yourself.
|
||||
// split will split a potentially large event that is larger than what the server
|
||||
// supports, into multiple events. split will ignore events that cannot be split, and
|
||||
// if the event isn't longer than what the server supports, it will just return an array
|
||||
// with 1 entry, the original event.
|
||||
func (e *Event) split(maxLength int) []*Event {
|
||||
if len(e.Params) < 1 || (e.Command != PRIVMSG && e.Command != NOTICE) {
|
||||
return []*Event{e}
|
||||
}
|
||||
|
||||
// Exclude source, even if it does exist, because the server will likely ignore the
|
||||
// sent source anyway.
|
||||
event := e.Copy()
|
||||
event.Source = nil
|
||||
|
||||
if event.LenOpts(false) < maxLength {
|
||||
return []*Event{e}
|
||||
}
|
||||
|
||||
results := []*Event{}
|
||||
|
||||
// Will force the length check to include " :". This will allow us to get the length
|
||||
// of the commands and necessary prefixes.
|
||||
text := event.Last()
|
||||
event.Params[len(event.Params)-1] = ""
|
||||
cmdLen := event.LenOpts(false)
|
||||
|
||||
var ok bool
|
||||
var ctcp *CTCPEvent
|
||||
if ok, ctcp = e.IsCTCP(); ok {
|
||||
if text == "" {
|
||||
return []*Event{e}
|
||||
}
|
||||
|
||||
text = ctcp.Text
|
||||
|
||||
// ctcpDelim's at start and end, and space between command and trailing text.
|
||||
maxLength -= len(ctcp.Command) + 4
|
||||
}
|
||||
|
||||
// If the command itself is longer than the limit, there is a problem. PRIVMSG should
|
||||
// be 1->1 per RFC. Just return the original message and let it be the user of the
|
||||
// libraries problem.
|
||||
if cmdLen > maxLength {
|
||||
return []*Event{e}
|
||||
}
|
||||
|
||||
// Split the text into correctly size segments, and make the necessary number of
|
||||
// events that duplicate the original event.
|
||||
for _, split := range splitMessage(text, maxLength-cmdLen) {
|
||||
if ctcp != nil {
|
||||
split = string(ctcpDelim) + ctcp.Command + string(eventSpace) + split + string(ctcpDelim)
|
||||
}
|
||||
clonedEvent := event.Copy()
|
||||
clonedEvent.Source = e.Source
|
||||
clonedEvent.Params[len(e.Params)-1] = split
|
||||
results = append(results, clonedEvent)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// Len calculates the length of the string representation of event (including tags).
|
||||
// Note that this will return the true length (even if longer than what IRC supports),
|
||||
// which may be useful if you are trying to check and see if a message is too long, to
|
||||
// trim it down yourself.
|
||||
func (e *Event) Len() (length int) {
|
||||
return e.LenOpts(true)
|
||||
}
|
||||
|
||||
// LenOpts calculates the length of the string representation of event (with a toggle
|
||||
// for tags). Note that this will return the true length (even if longer than what IRC
|
||||
// supports), which may be useful if you are trying to check and see if a message is
|
||||
// too long, to trim it down yourself.
|
||||
func (e *Event) LenOpts(includeTags bool) (length int) {
|
||||
if e.Tags != nil {
|
||||
// Include tags and trailing space.
|
||||
length = e.Tags.Len() + 1
|
||||
@@ -248,7 +351,7 @@ func (e *Event) Len() (length int) {
|
||||
|
||||
// If param contains a space or it's empty, it's trailing, so it should be
|
||||
// prefixed with a colon (:).
|
||||
if i == len(e.Params)-1 && (strings.Contains(e.Params[i], " ") || strings.HasPrefix(e.Params[i], ":") || e.Params[i] == "") {
|
||||
if i == len(e.Params)-1 && (strings.Contains(e.Params[i], " ") || e.Params[i] == "" || strings.HasPrefix(e.Params[i], ":")) {
|
||||
length++
|
||||
}
|
||||
}
|
||||
@@ -259,10 +362,6 @@ func (e *Event) Len() (length int) {
|
||||
|
||||
// Bytes returns a []byte representation of event. Strips all newlines and
|
||||
// carriage returns.
|
||||
//
|
||||
// Per RFC2812 section 2.3, messages should not exceed 512 characters in
|
||||
// length. This method forces that limit by discarding any characters
|
||||
// exceeding the length limit.
|
||||
func (e *Event) Bytes() []byte {
|
||||
buffer := new(bytes.Buffer)
|
||||
|
||||
@@ -284,7 +383,7 @@ func (e *Event) Bytes() []byte {
|
||||
// Space separated list of arguments.
|
||||
if len(e.Params) > 0 {
|
||||
for i := 0; i < len(e.Params); i++ {
|
||||
if i == len(e.Params)-1 && (strings.Contains(e.Params[i], " ") || strings.HasPrefix(e.Params[i], ":") || e.Params[i] == "") {
|
||||
if i == len(e.Params)-1 && (strings.Contains(e.Params[i], " ") || e.Params[i] == "" || strings.HasPrefix(e.Params[i], ":")) {
|
||||
buffer.WriteString(string(eventSpace) + string(messagePrefix) + e.Params[i])
|
||||
continue
|
||||
}
|
||||
@@ -292,11 +391,6 @@ func (e *Event) Bytes() []byte {
|
||||
}
|
||||
}
|
||||
|
||||
// We need the limit the buffer length.
|
||||
if buffer.Len() > (maxLength) {
|
||||
buffer.Truncate(maxLength)
|
||||
}
|
||||
|
||||
// If we truncated in the middle of a utf8 character, we need to remove
|
||||
// the other (now invalid) bytes.
|
||||
out := bytes.ToValidUTF8(buffer.Bytes(), nil)
|
||||
|
||||
216
vendor/github.com/lrstanley/girc/format.go
generated
vendored
216
vendor/github.com/lrstanley/girc/format.go
generated
vendored
@@ -7,13 +7,21 @@ package girc
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const (
|
||||
fmtOpenChar = '{'
|
||||
fmtCloseChar = '}'
|
||||
fmtOpenChar = '{'
|
||||
fmtCloseChar = '}'
|
||||
maxWordSplitLength = 30
|
||||
)
|
||||
|
||||
var (
|
||||
reCode = regexp.MustCompile(`(\x02|\x1d|\x0f|\x03|\x16|\x1f|\x01)`)
|
||||
reColor = regexp.MustCompile(`\x03([019]?\d(,[019]?\d)?)`)
|
||||
)
|
||||
|
||||
var fmtColors = map[string]int{
|
||||
@@ -66,9 +74,9 @@ var fmtCodes = map[string]string{
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// client.Message("#channel", Fmt("{red}{b}Hello {red,blue}World{c}"))
|
||||
// client.Message("#channel", Fmt("{red}{b}Hello {red,blue}World{c}"))
|
||||
func Fmt(text string) string {
|
||||
var last = -1
|
||||
last := -1
|
||||
for i := 0; i < len(text); i++ {
|
||||
if text[i] == fmtOpenChar {
|
||||
last = i
|
||||
@@ -136,16 +144,12 @@ func TrimFmt(text string) string {
|
||||
return text
|
||||
}
|
||||
|
||||
// This is really the only fastest way of doing this (marginally better than
|
||||
// actually trying to parse it manually.)
|
||||
var reStripColor = regexp.MustCompile(`\x03([019]?\d(,[019]?\d)?)?`)
|
||||
|
||||
// StripRaw tries to strip all ASCII format codes that are used for IRC.
|
||||
// Primarily, foreground/background colors, and other control bytes like
|
||||
// reset, bold, italic, reverse, etc. This also is done in a specific way
|
||||
// in order to ensure no truncation of other non-irc formatting.
|
||||
func StripRaw(text string) string {
|
||||
text = reStripColor.ReplaceAllString(text, "")
|
||||
text = reColor.ReplaceAllString(text, "")
|
||||
|
||||
for _, code := range fmtCodes {
|
||||
text = strings.ReplaceAll(text, code, "")
|
||||
@@ -164,12 +168,12 @@ func StripRaw(text string) string {
|
||||
// all ASCII printable chars. This function will NOT do that for
|
||||
// compatibility reasons.
|
||||
//
|
||||
// channel = ( "#" / "+" / ( "!" channelid ) / "&" ) chanstring
|
||||
// [ ":" chanstring ]
|
||||
// chanstring = 0x01-0x07 / 0x08-0x09 / 0x0B-0x0C / 0x0E-0x1F / 0x21-0x2B
|
||||
// chanstring = / 0x2D-0x39 / 0x3B-0xFF
|
||||
// ; any octet except NUL, BELL, CR, LF, " ", "," and ":"
|
||||
// channelid = 5( 0x41-0x5A / digit ) ; 5( A-Z / 0-9 )
|
||||
// channel = ( "#" / "+" / ( "!" channelid ) / "&" ) chanstring
|
||||
// [ ":" chanstring ]
|
||||
// chanstring = 0x01-0x07 / 0x08-0x09 / 0x0B-0x0C / 0x0E-0x1F / 0x21-0x2B
|
||||
// chanstring = / 0x2D-0x39 / 0x3B-0xFF
|
||||
// ; any octet except NUL, BELL, CR, LF, " ", "," and ":"
|
||||
// channelid = 5( 0x41-0x5A / digit ) ; 5( A-Z / 0-9 )
|
||||
func IsValidChannel(channel string) bool {
|
||||
if len(channel) <= 1 || len(channel) > 50 {
|
||||
return false
|
||||
@@ -214,10 +218,10 @@ func IsValidChannel(channel string) bool {
|
||||
// IsValidNick validates an IRC nickname. Note that this does not validate
|
||||
// IRC nickname length.
|
||||
//
|
||||
// nickname = ( letter / special ) *8( letter / digit / special / "-" )
|
||||
// letter = 0x41-0x5A / 0x61-0x7A
|
||||
// digit = 0x30-0x39
|
||||
// special = 0x5B-0x60 / 0x7B-0x7D
|
||||
// nickname = ( letter / special ) *8( letter / digit / special / "-" )
|
||||
// letter = 0x41-0x5A / 0x61-0x7A
|
||||
// digit = 0x30-0x39
|
||||
// special = 0x5B-0x60 / 0x7B-0x7D
|
||||
func IsValidNick(nick string) bool {
|
||||
if nick == "" {
|
||||
return false
|
||||
@@ -253,8 +257,9 @@ func IsValidNick(nick string) bool {
|
||||
// not be supported on all networks. Some limit this to only a single period.
|
||||
//
|
||||
// Per RFC:
|
||||
// user = 1*( %x01-09 / %x0B-0C / %x0E-1F / %x21-3F / %x41-FF )
|
||||
// ; any octet except NUL, CR, LF, " " and "@"
|
||||
//
|
||||
// user = 1*( %x01-09 / %x0B-0C / %x0E-1F / %x21-3F / %x41-FF )
|
||||
// ; any octet except NUL, CR, LF, " " and "@"
|
||||
func IsValidUser(name string) bool {
|
||||
if name == "" {
|
||||
return false
|
||||
@@ -350,3 +355,172 @@ func Glob(input, match string) bool {
|
||||
// Check suffix last.
|
||||
return trailingGlob || strings.HasSuffix(input, parts[last])
|
||||
}
|
||||
|
||||
// sliceInsert inserts a string into a slice at a specific index, while trying
|
||||
// to avoid as many allocations as possible.
|
||||
func sliceInsert(input []string, i int, v ...string) []string {
|
||||
total := len(input) + len(v)
|
||||
if total <= cap(input) {
|
||||
output := input[:total]
|
||||
copy(output[i+len(v):], input[i:])
|
||||
copy(output[i:], v)
|
||||
return output
|
||||
}
|
||||
output := make([]string, total)
|
||||
copy(output, input[:i])
|
||||
copy(output[i:], v)
|
||||
copy(output[i+len(v):], input[i:])
|
||||
return output
|
||||
}
|
||||
|
||||
// splitMessage is a text splitter that takes into consideration a few things:
|
||||
// - Ensuring the returned text is no longer than maxWidth.
|
||||
// - Attempting to split at the closest word boundary, while still staying inside
|
||||
// of the specific maxWidth.
|
||||
// - if there is no good word boundary for longer words (or e.g. links, raw data, etc)
|
||||
// that are above maxWordSplitLength characters, split the word into chunks to fit the
|
||||
//
|
||||
// maximum width.
|
||||
func splitMessage(input string, maxWidth int) (output []string) {
|
||||
input = strings.ToValidUTF8(input, "?")
|
||||
|
||||
words := strings.FieldsFunc(strings.TrimSpace(input), func(r rune) bool {
|
||||
switch r { // Same as unicode.IsSpace, but without ctrl/lf.
|
||||
case '\t', '\v', '\f', ' ', 0x85, 0xA0:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
output = []string{""}
|
||||
codes := []string{}
|
||||
|
||||
var lastColor string
|
||||
var match []string
|
||||
|
||||
for i := 0; i < len(words); i++ {
|
||||
j := strings.IndexAny(words[i], "\n\r")
|
||||
if j == -1 {
|
||||
continue
|
||||
}
|
||||
|
||||
word := words[i]
|
||||
words[i] = word[:j]
|
||||
|
||||
words = sliceInsert(words, i+1, "", strings.TrimLeft(word[j:], "\n\r"))
|
||||
}
|
||||
|
||||
for _, word := range words {
|
||||
// Used in place of a single newline.
|
||||
if word == "" {
|
||||
// Last line was already empty or already only had control characters.
|
||||
if output[len(output)-1] == "" || output[len(output)-1] == lastColor+word {
|
||||
continue
|
||||
}
|
||||
|
||||
output = append(output, strings.Join(codes, "")+lastColor+word)
|
||||
continue
|
||||
}
|
||||
|
||||
// Keep track of the last used color codes.
|
||||
match = reColor.FindAllString(word, -1)
|
||||
if len(match) > 0 {
|
||||
lastColor = match[len(match)-1]
|
||||
}
|
||||
|
||||
// Find all sequence codes -- this approach isn't perfect (ideally, a lexer
|
||||
// should be used to track each exact type of code), but it's good enough for
|
||||
// most cases.
|
||||
match = reCode.FindAllString(word, -1)
|
||||
if len(match) > 0 {
|
||||
for _, m := range match {
|
||||
// Reset was used, so clear all codes.
|
||||
if m == fmtCodes["reset"] {
|
||||
lastColor = ""
|
||||
codes = []string{}
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if we already have the code, and if so, remove it (closing).
|
||||
contains := false
|
||||
for i := 0; i < len(codes); i++ {
|
||||
if m == codes[i] {
|
||||
contains = true
|
||||
codes = append(codes[:i], codes[i+1:]...)
|
||||
|
||||
// If it's a closing color code, reset the last used color
|
||||
// as well.
|
||||
if m == fmtCodes["clear"] {
|
||||
lastColor = ""
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Track the new code, unless it's a color clear but we aren't
|
||||
// tracking a color right now.
|
||||
if !contains && (lastColor == "" || m != fmtCodes["clear"]) {
|
||||
codes = append(codes, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkappend:
|
||||
|
||||
// Check if we can append, otherwise we must split.
|
||||
if 1+utf8.RuneCountInString(word)+utf8.RuneCountInString(output[len(output)-1]) < maxWidth {
|
||||
if output[len(output)-1] != "" {
|
||||
output[len(output)-1] += " "
|
||||
}
|
||||
output[len(output)-1] += word
|
||||
continue
|
||||
}
|
||||
|
||||
// If the word can fit on a line by itself, check if it's a url. If it is,
|
||||
// put it on it's own line.
|
||||
if utf8.RuneCountInString(word+strings.Join(codes, "")+lastColor) < maxWidth {
|
||||
if _, err := url.Parse(word); err == nil {
|
||||
output = append(output, strings.Join(codes, "")+lastColor+word)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Check to see if we can split by misc symbols, but must be at least a few
|
||||
// characters long to be split by it.
|
||||
if j := strings.IndexAny(word, "-+_=|/~:;,."); j > 3 && 1+utf8.RuneCountInString(word[0:j])+utf8.RuneCountInString(output[len(output)-1]) < maxWidth {
|
||||
if output[len(output)-1] != "" {
|
||||
output[len(output)-1] += " "
|
||||
}
|
||||
output[len(output)-1] += word[0:j]
|
||||
word = word[j+1:]
|
||||
goto checkappend
|
||||
}
|
||||
|
||||
// If the word is longer than is acceptable to just put on the next line,
|
||||
// split it into chunks. Also don't split the word if only a few characters
|
||||
// left of the word would be on the next line.
|
||||
if 1+utf8.RuneCountInString(word) > maxWordSplitLength && maxWidth-utf8.RuneCountInString(output[len(output)-1]) > 5 {
|
||||
left := maxWidth - utf8.RuneCountInString(output[len(output)-1]) - 1 // -1 for the space
|
||||
|
||||
if output[len(output)-1] != "" {
|
||||
output[len(output)-1] += " "
|
||||
}
|
||||
output[len(output)-1] += word[0:left]
|
||||
word = word[left:]
|
||||
goto checkappend
|
||||
}
|
||||
|
||||
left := maxWidth - utf8.RuneCountInString(output[len(output)-1])
|
||||
output[len(output)-1] += word[0:left]
|
||||
|
||||
output = append(output, strings.Join(codes, "")+lastColor)
|
||||
word = word[left:]
|
||||
goto checkappend
|
||||
}
|
||||
|
||||
for i := 0; i < len(output); i++ {
|
||||
output[i] = strings.ToValidUTF8(output[i], "?")
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
67
vendor/github.com/lrstanley/girc/internal/ctxgroup/ctxgroup.go
generated
vendored
Normal file
67
vendor/github.com/lrstanley/girc/internal/ctxgroup/ctxgroup.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 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.
|
||||
|
||||
package ctxgroup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A Group is a collection of goroutines working on subtasks that are part of
|
||||
// the same overall task.
|
||||
type Group struct {
|
||||
ctx context.Context
|
||||
cancel func()
|
||||
|
||||
wg sync.WaitGroup
|
||||
|
||||
errOnce sync.Once
|
||||
err error
|
||||
}
|
||||
|
||||
// New returns a new Group and an associated context derived from ctx.
|
||||
// Obtain the derived context from calling Group.Context().
|
||||
//
|
||||
// The derived context is canceled the first time a function passed to Go
|
||||
// returns a non-nil error or the first time Wait returns, whichever occurs
|
||||
// first.
|
||||
func New(ctx context.Context) *Group {
|
||||
nctx, cancel := context.WithCancel(ctx)
|
||||
return &Group{ctx: nctx, cancel: cancel}
|
||||
}
|
||||
|
||||
// Context returns the context for this group. It may be canceled by the first
|
||||
// function to return a non-nil error.
|
||||
func (g *Group) Context() context.Context {
|
||||
return g.ctx
|
||||
}
|
||||
|
||||
// Wait blocks until all function calls from the Go method have returned, then
|
||||
// returns the first non-nil error (if any) from them.
|
||||
func (g *Group) Wait() error {
|
||||
g.wg.Wait()
|
||||
if g.cancel != nil {
|
||||
g.cancel()
|
||||
}
|
||||
return g.err
|
||||
}
|
||||
|
||||
// Go calls the given function in a new goroutine. The first call to return a
|
||||
// non-nil error cancels the group; its error will be returned by Wait.
|
||||
func (g *Group) Go(f func(ctx context.Context) error) {
|
||||
g.wg.Add(1)
|
||||
go func() {
|
||||
defer g.wg.Done()
|
||||
|
||||
if err := f(g.ctx); err != nil {
|
||||
g.errOnce.Do(func() {
|
||||
g.err = err
|
||||
if g.cancel != nil {
|
||||
g.cancel()
|
||||
}
|
||||
})
|
||||
}
|
||||
}()
|
||||
}
|
||||
15
vendor/github.com/lrstanley/girc/modes.go
generated
vendored
15
vendor/github.com/lrstanley/girc/modes.go
generated
vendored
@@ -118,13 +118,14 @@ func (c *CModes) Get(mode string) (args string, ok bool) {
|
||||
}
|
||||
|
||||
// hasArg checks to see if the mode supports arguments. What ones support this?:
|
||||
// A = Mode that adds or removes a nick or address to a list. Always has a parameter.
|
||||
// B = Mode that changes a setting and always has a parameter.
|
||||
// C = Mode that changes a setting and only has a parameter when set.
|
||||
// D = Mode that changes a setting and never has a parameter.
|
||||
// Note: Modes of type A return the list when there is no parameter present.
|
||||
// Note: Some clients assumes that any mode not listed is of type D.
|
||||
// Note: Modes in PREFIX are not listed but could be considered type B.
|
||||
//
|
||||
// A = Mode that adds or removes a nick or address to a list. Always has a parameter.
|
||||
// B = Mode that changes a setting and always has a parameter.
|
||||
// C = Mode that changes a setting and only has a parameter when set.
|
||||
// D = Mode that changes a setting and never has a parameter.
|
||||
// Note: Modes of type A return the list when there is no parameter present.
|
||||
// Note: Some clients assumes that any mode not listed is of type D.
|
||||
// Note: Modes in PREFIX are not listed but could be considered type B.
|
||||
func (c *CModes) hasArg(set bool, mode byte) (hasArgs, isSetting bool) {
|
||||
if len(c.raw) < 1 {
|
||||
return false, true
|
||||
|
||||
15
vendor/github.com/lrstanley/girc/state.go
generated
vendored
15
vendor/github.com/lrstanley/girc/state.go
generated
vendored
@@ -28,10 +28,21 @@ type state struct {
|
||||
// last capability check. These will get sent once we have received the
|
||||
// last capability list command from the server.
|
||||
tmpCap map[string]map[string]string
|
||||
|
||||
// serverOptions are the standard capabilities and configurations
|
||||
// supported by the server at connection time. This also includes
|
||||
// RPL_ISUPPORT entries.
|
||||
serverOptions map[string]string
|
||||
|
||||
// maxLineLength defines how long before we truncate (or split) messages.
|
||||
// DefaultMaxLineLength is what is used by default, as this is going to be a common
|
||||
// standard. However, protocols like IRCv3, or ISUPPORT can override this.
|
||||
maxLineLength int
|
||||
|
||||
// maxPrefixLength defines the estimated prefix length (":nick!user@host ") that
|
||||
// we can use to calculate line splits.
|
||||
maxPrefixLength int
|
||||
|
||||
// motd is the servers message of the day.
|
||||
motd string
|
||||
|
||||
@@ -51,9 +62,11 @@ func (s *state) reset(initial bool) {
|
||||
s.host = ""
|
||||
s.channels = make(map[string]*Channel)
|
||||
s.users = make(map[string]*User)
|
||||
s.serverOptions = make(map[string]string)
|
||||
s.enabledCap = make(map[string]map[string]string)
|
||||
s.tmpCap = make(map[string]map[string]string)
|
||||
s.serverOptions = make(map[string]string)
|
||||
s.maxLineLength = DefaultMaxLineLength
|
||||
s.maxPrefixLength = DefaultMaxPrefixLength
|
||||
s.motd = ""
|
||||
|
||||
if initial {
|
||||
|
||||
Reference in New Issue
Block a user