~emersion/soju

106d40dcd489f58bf35a7bf095dad137c45d03fc — Simon Ser 20 days ago a0e9c10
Upgrade to gopkg.in/irc.v4
M conn.go => conn.go +1 -1
@@ 11,7 11,7 @@ import (
	"unicode"

	"golang.org/x/time/rate"
	"gopkg.in/irc.v3"
	"gopkg.in/irc.v4"
	"nhooyr.io/websocket"
)


M downstream.go => downstream.go +26 -26
@@ 15,7 15,7 @@ import (

	"github.com/SherClockHolmes/webpush-go"
	"github.com/emersion/go-sasl"
	"gopkg.in/irc.v3"
	"gopkg.in/irc.v4"

	"git.sr.ht/~emersion/soju/database"
	"git.sr.ht/~emersion/soju/msgstore"


@@ 120,17 120,17 @@ func fillNetworkAddrAttrs(attrs irc.Tags, network *database.Network) {
	hasHostPort := true
	switch u.Scheme {
	case "ircs":
		attrs["tls"] = irc.TagValue("1")
		attrs["tls"] = "1"
	case "irc+insecure":
		attrs["tls"] = irc.TagValue("0")
		attrs["tls"] = "0"
	default: // e.g. unix://
		hasHostPort = false
	}
	if host, port, err := net.SplitHostPort(u.Host); err == nil && hasHostPort {
		attrs["host"] = irc.TagValue(host)
		attrs["port"] = irc.TagValue(port)
		attrs["host"] = host
		attrs["port"] = port
	} else if hasHostPort {
		attrs["host"] = irc.TagValue(u.Host)
		attrs["host"] = u.Host
	}
}



@@ 141,20 141,20 @@ func getNetworkAttrs(network *network) irc.Tags {
	}

	attrs := irc.Tags{
		"name":     irc.TagValue(network.GetName()),
		"state":    irc.TagValue(state),
		"nickname": irc.TagValue(database.GetNick(&network.user.User, &network.Network)),
		"name":     network.GetName(),
		"state":    state,
		"nickname": database.GetNick(&network.user.User, &network.Network),
	}

	if network.Username != "" {
		attrs["username"] = irc.TagValue(network.Username)
		attrs["username"] = network.Username
	}
	if realname := database.GetRealname(&network.user.User, &network.Network); realname != "" {
		attrs["realname"] = irc.TagValue(realname)
		attrs["realname"] = realname
	}

	if network.lastError != nil {
		attrs["error"] = irc.TagValue(network.lastError.Error())
		attrs["error"] = network.lastError.Error()
	}

	fillNetworkAddrAttrs(attrs, &network.Network)


@@ 524,7 524,7 @@ func (dc *downstreamConn) SendMessage(msg *irc.Message) {
	dc.conn.SendMessage(context.TODO(), msg)
}

func (dc *downstreamConn) SendBatch(typ string, params []string, tags irc.Tags, f func(batchRef irc.TagValue)) {
func (dc *downstreamConn) SendBatch(typ string, params []string, tags irc.Tags, f func(batchRef string)) {
	dc.lastBatchRef++
	ref := fmt.Sprintf("%v", dc.lastBatchRef)



@@ 537,7 537,7 @@ func (dc *downstreamConn) SendBatch(typ string, params []string, tags irc.Tags, 
		})
	}

	f(irc.TagValue(ref))
	f(ref)

	if dc.caps.IsEnabled("batch") {
		dc.SendMessage(&irc.Message{


@@ 1463,7 1463,7 @@ func (dc *downstreamConn) welcome(ctx context.Context) error {
	}

	if dc.caps.IsEnabled("soju.im/bouncer-networks-notify") {
		dc.SendBatch("soju.im/bouncer-networks", nil, nil, func(batchRef irc.TagValue) {
		dc.SendBatch("soju.im/bouncer-networks", nil, nil, func(batchRef string) {
			for _, network := range dc.user.networks {
				idStr := fmt.Sprintf("%v", network.ID)
				attrs := getNetworkAttrs(network)


@@ 1569,7 1569,7 @@ func (dc *downstreamConn) sendTargetBacklog(ctx context.Context, net *network, t
		return
	}

	dc.SendBatch("chathistory", []string{target}, nil, func(batchRef irc.TagValue) {
	dc.SendBatch("chathistory", []string{target}, nil, func(batchRef string) {
		for _, msg := range history {
			if ch != nil && ch.Detached {
				if net.detachedMessageNeedsRelay(ch, msg) {


@@ 2281,7 2281,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
				dc.logger.Printf("broadcasting bouncer-wide %v: %v", msg.Command, text)

				broadcastTags := tags.Copy()
				broadcastTags["time"] = irc.TagValue(xirc.FormatServerTime(time.Now()))
				broadcastTags["time"] = xirc.FormatServerTime(time.Now())
				broadcastMsg := &irc.Message{
					Tags:    broadcastTags,
					Prefix:  servicePrefix,


@@ 2307,7 2307,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
			if casemapASCII(name) == serviceNickCM {
				if dc.caps.IsEnabled("echo-message") {
					echoTags := tags.Copy()
					echoTags["time"] = irc.TagValue(xirc.FormatServerTime(time.Now()))
					echoTags["time"] = xirc.FormatServerTime(time.Now())
					dc.SendMessage(&irc.Message{
						Tags:    echoTags,
						Prefix:  dc.prefix(),


@@ 2351,9 2351,9 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
				}

				echoTags := tags.Copy()
				echoTags["time"] = irc.TagValue(xirc.FormatServerTime(time.Now()))
				echoTags["time"] = xirc.FormatServerTime(time.Now())
				if uc.account != "" {
					echoTags["account"] = irc.TagValue(uc.account)
					echoTags["account"] = uc.account
				}
				echoMsg := &irc.Message{
					Tags: echoTags,


@@ 2591,7 2591,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
			if dc.network == nil {
				// Either an unbound bouncer network, in which case we should return no targets,
				// or a multi-upstream downstream, but we don't support CHATHISTORY TARGETS for those yet.
				dc.SendBatch("draft/chathistory-targets", nil, nil, func(batchRef irc.TagValue) {})
				dc.SendBatch("draft/chathistory-targets", nil, nil, func(batchRef string) {})
				return nil
			}
			if err := parseMessageParams(msg, nil, &boundsStr[0], &boundsStr[1], &limitStr); err != nil {


@@ 2607,7 2607,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.

		// We don't save history for our service
		if casemapASCII(target) == serviceNickCM {
			dc.SendBatch("chathistory", []string{target}, nil, func(batchRef irc.TagValue) {})
			dc.SendBatch("chathistory", []string{target}, nil, func(batchRef string) {})
			return nil
		}



@@ 2690,7 2690,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
				}}
			}

			dc.SendBatch("draft/chathistory-targets", nil, nil, func(batchRef irc.TagValue) {
			dc.SendBatch("draft/chathistory-targets", nil, nil, func(batchRef string) {
				for _, target := range targets {
					if ch := network.channels.Get(target.Name); ch != nil && ch.Detached {
						continue


@@ 2712,7 2712,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
			return newChatHistoryError(subcommand, target)
		}

		dc.SendBatch("chathistory", []string{target}, nil, func(batchRef irc.TagValue) {
		dc.SendBatch("chathistory", []string{target}, nil, func(batchRef string) {
			for _, msg := range history {
				msg.Tags["batch"] = batchRef
				dc.SendMessage(msg)


@@ 2903,7 2903,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
			}}
		}

		dc.SendBatch("soju.im/search", nil, nil, func(batchRef irc.TagValue) {
		dc.SendBatch("soju.im/search", nil, nil, func(batchRef string) {
			for _, msg := range messages {
				msg.Tags["batch"] = batchRef
				dc.SendMessage(msg)


@@ 2922,7 2922,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
				Params:  []string{"BOUNCER", "REGISTRATION_IS_COMPLETED", "BIND", "Cannot bind to a network after registration"},
			}}
		case "LISTNETWORKS":
			dc.SendBatch("soju.im/bouncer-networks", nil, nil, func(batchRef irc.TagValue) {
			dc.SendBatch("soju.im/bouncer-networks", nil, nil, func(batchRef string) {
				for _, network := range dc.user.networks {
					idStr := fmt.Sprintf("%v", network.ID)
					attrs := getNetworkAttrs(network)

M go.mod => go.mod +1 -1
@@ 16,7 16,7 @@ require (
	golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2 // indirect
	golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect
	golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9
	gopkg.in/irc.v3 v3.1.4
	gopkg.in/irc.v4 v4.0.0
	modernc.org/sqlite v1.18.2
	nhooyr.io/websocket v1.8.7
)

M go.sum => go.sum +8 -4
@@ 251,11 251,14 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=


@@ 551,8 554,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/irc.v3 v3.1.4 h1:DYGMRFbtseXEh+NadmMUFzMraqyuUj4I3iWYFEzDZPc=
gopkg.in/irc.v3 v3.1.4/go.mod h1:shO2gz8+PVeS+4E6GAny88Z0YVVQSxQghdrMVGQsR9s=
gopkg.in/irc.v4 v4.0.0 h1:5jsLkU2Tg+R2nGNqmkGCrciasyi4kNkDXhyZD+C31yY=
gopkg.in/irc.v4 v4.0.0/go.mod h1:BfjDz9MmuWW6OZY7iq4naOhudO8+QQCdO4Ko18jcsRE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=


@@ 561,8 564,9 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

M irc.go => irc.go +1 -1
@@ 7,7 7,7 @@ import (
	"unicode"
	"unicode/utf8"

	"gopkg.in/irc.v3"
	"gopkg.in/irc.v4"

	"git.sr.ht/~emersion/soju/database"
	"git.sr.ht/~emersion/soju/xirc"

M msgstore/fs.go => msgstore/fs.go +3 -3
@@ 12,7 12,7 @@ import (
	"time"

	"git.sr.ht/~sircmpwn/go-bare"
	"gopkg.in/irc.v3"
	"gopkg.in/irc.v4"

	"git.sr.ht/~emersion/soju/database"
	"git.sr.ht/~emersion/soju/xirc"


@@ 395,8 395,8 @@ func (ms *fsMessageStore) parseMessage(line string, network *database.Network, e
	t := time.Date(year, month, day, hour, minute, second, 0, time.Local)

	msg := &irc.Message{
		Tags: map[string]irc.TagValue{
			"time": irc.TagValue(xirc.FormatServerTime(t)),
		Tags: map[string]string{
			"time": xirc.FormatServerTime(t),
		},
		Prefix:  prefix,
		Command: cmd,

M msgstore/memory.go => msgstore/memory.go +1 -1
@@ 6,7 6,7 @@ import (
	"time"

	"git.sr.ht/~sircmpwn/go-bare"
	"gopkg.in/irc.v3"
	"gopkg.in/irc.v4"

	"git.sr.ht/~emersion/soju/database"
)

M msgstore/msgstore.go => msgstore/msgstore.go +1 -1
@@ 8,7 8,7 @@ import (
	"time"

	"git.sr.ht/~sircmpwn/go-bare"
	"gopkg.in/irc.v3"
	"gopkg.in/irc.v4"

	"git.sr.ht/~emersion/soju/database"
)

M server.go => server.go +1 -1
@@ 17,7 17,7 @@ import (
	"github.com/SherClockHolmes/webpush-go"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
	"gopkg.in/irc.v3"
	"gopkg.in/irc.v4"
	"nhooyr.io/websocket"

	"git.sr.ht/~emersion/soju/config"

M server_test.go => server_test.go +1 -1
@@ 7,7 7,7 @@ import (
	"testing"

	"golang.org/x/crypto/bcrypt"
	"gopkg.in/irc.v3"
	"gopkg.in/irc.v4"

	"git.sr.ht/~emersion/soju/database"
)

M service.go => service.go +1 -1
@@ 16,7 16,7 @@ import (
	"unicode"

	"golang.org/x/crypto/bcrypt"
	"gopkg.in/irc.v3"
	"gopkg.in/irc.v4"

	"git.sr.ht/~emersion/soju/database"
)

M upstream.go => upstream.go +6 -6
@@ 16,7 16,7 @@ import (
	"time"

	"github.com/emersion/go-sasl"
	"gopkg.in/irc.v3"
	"gopkg.in/irc.v4"

	"git.sr.ht/~emersion/soju/database"
	"git.sr.ht/~emersion/soju/xirc"


@@ 452,13 452,13 @@ func (uc *upstreamConn) parseMembershipPrefix(s string) (ms xirc.MembershipSet, 

func (uc *upstreamConn) handleMessage(ctx context.Context, msg *irc.Message) error {
	var label string
	if l, ok := msg.GetTag("label"); ok {
	if l, ok := msg.Tags["label"]; ok {
		label = l
		delete(msg.Tags, "label")
	}

	var msgBatch *upstreamBatch
	if batchName, ok := msg.GetTag("batch"); ok {
	if batchName, ok := msg.Tags["batch"]; ok {
		b, ok := uc.batches[batchName]
		if !ok {
			return fmt.Errorf("unexpected batch reference: batch was not defined: %q", batchName)


@@ 487,7 487,7 @@ func (uc *upstreamConn) handleMessage(ctx context.Context, msg *irc.Message) err
	}

	if _, ok := msg.Tags["time"]; !ok && !isNumeric(msg.Command) {
		msg.Tags["time"] = irc.TagValue(xirc.FormatServerTime(time.Now()))
		msg.Tags["time"] = xirc.FormatServerTime(time.Now())
	}

	switch msg.Command {


@@ 1877,9 1877,9 @@ func (uc *upstreamConn) SendMessage(ctx context.Context, msg *irc.Message) {
func (uc *upstreamConn) SendMessageLabeled(ctx context.Context, downstreamID uint64, msg *irc.Message) {
	if uc.caps.IsEnabled("labeled-response") {
		if msg.Tags == nil {
			msg.Tags = make(map[string]irc.TagValue)
			msg.Tags = make(irc.Tags)
		}
		msg.Tags["label"] = irc.TagValue(fmt.Sprintf("sd-%d-%d", downstreamID, uc.nextLabelID))
		msg.Tags["label"] = fmt.Sprintf("sd-%d-%d", downstreamID, uc.nextLabelID)
		uc.nextLabelID++
	}
	uc.SendMessage(ctx, msg)

M user.go => user.go +3 -3
@@ 14,7 14,7 @@ import (
	"time"

	"github.com/SherClockHolmes/webpush-go"
	"gopkg.in/irc.v3"
	"gopkg.in/irc.v4"

	"git.sr.ht/~emersion/soju/database"
	"git.sr.ht/~emersion/soju/msgstore"


@@ 641,7 641,7 @@ func (u *user) run() {
			}
			net.lastError = e.err
			u.notifyBouncerNetworkState(net.ID, irc.Tags{
				"error": irc.TagValue(net.lastError.Error()),
				"error": net.lastError.Error(),
			})
		case eventUpstreamError:
			uc := e.uc


@@ 651,7 651,7 @@ func (u *user) run() {
			})
			uc.network.lastError = e.err
			u.notifyBouncerNetworkState(uc.network.ID, irc.Tags{
				"error": irc.TagValue(uc.network.lastError.Error()),
				"error": uc.network.lastError.Error(),
			})
		case eventUpstreamMessage:
			msg, uc := e.msg, e.uc

M xirc/genmsg.go => xirc/genmsg.go +1 -1
@@ 6,7 6,7 @@ import (
	"sort"
	"strings"

	"gopkg.in/irc.v3"
	"gopkg.in/irc.v4"
)

func GenerateJoin(channels, keys []string) []*irc.Message {

M xirc/whox.go => xirc/whox.go +1 -1
@@ 1,7 1,7 @@
package xirc

import (
	"gopkg.in/irc.v3"
	"gopkg.in/irc.v4"
)

// whoxFields is the list of all WHOX field letters, by order of appearance in

M xirc/xirc.go => xirc/xirc.go +1 -1
@@ 6,7 6,7 @@ import (
	"strings"
	"time"

	"gopkg.in/irc.v3"
	"gopkg.in/irc.v4"
)

const (