Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 14 additions & 13 deletions bridge/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,20 @@ const (
const ParentIDNotFound = "msg-parent-not-found"

type Message struct {
Text string `json:"text"`
Channel string `json:"channel"`
Username string `json:"username"`
UserID string `json:"userid"` // userid on the bridge
Avatar string `json:"avatar"`
Account string `json:"account"`
Event string `json:"event"`
Protocol string `json:"protocol"`
Gateway string `json:"gateway"`
ParentID string `json:"parent_id"`
Timestamp time.Time `json:"timestamp"`
ID string `json:"id"`
Extra map[string][]interface{}
Text string `json:"text"`
Channel string `json:"channel"`
Username string `json:"username"`
UserID string `json:"userid"` // userid on the bridge
Avatar string `json:"avatar"`
Account string `json:"account"`
Event string `json:"event"`
Protocol string `json:"protocol"`
Gateway string `json:"gateway"`
ParentID string `json:"parent_id"`
ParentText string `json:"parent_text"`
Timestamp time.Time `json:"timestamp"`
ID string `json:"id"`
Extra map[string][]interface{}
}

func (m Message) ParentNotFound() bool {
Expand Down
97 changes: 75 additions & 22 deletions bridge/xmpp/xmpp.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"sync"
"time"

lru "github.com/hashicorp/golang-lru/v2"
"github.com/jpillora/backoff"
"github.com/matterbridge-org/matterbridge/bridge"
"github.com/matterbridge-org/matterbridge/bridge/config"
Expand All @@ -29,10 +30,12 @@ type UploadBufferEntry struct {
type Bxmpp struct {
*bridge.Config

startTime time.Time
xc *xmpp.Client
xmppMap map[string]string
connected bool
startTime time.Time
xc *xmpp.Client
xmppMap map[string]string
connected bool
stanzaIDs *lru.Cache[string, string]
replyHeaders *lru.Cache[string, xmpp.Reply]
sync.RWMutex

avatarAvailability map[string]bool
Expand All @@ -58,12 +61,23 @@ type Bxmpp struct {
}

func New(cfg *bridge.Config) bridge.Bridger {
stanzaIDs, err := lru.New[string, string](5000)
if err != nil {
cfg.Log.Fatalf("Could not create LRU cache: %v", err)
}
replyHeaders, err := lru.New[string, xmpp.Reply](5000)
if err != nil {
cfg.Log.Fatalf("Could not create LRU cache: %v", err)
}

return &Bxmpp{
Config: cfg,
xmppMap: make(map[string]string),
avatarAvailability: make(map[string]bool),
avatarMap: make(map[string]string),
httpUploadBuffer: make(map[string]*UploadBufferEntry),
stanzaIDs: stanzaIDs,
replyHeaders: replyHeaders,
}
}

Expand Down Expand Up @@ -142,19 +156,27 @@ func (b *Bxmpp) Send(msg config.Message) (string, error) {
}
}

// XEP-0461: populate reply fields if this message is a reply.
var reply *xmpp.Reply
if msg.ParentValid() {
if _reply, ok := b.replyHeaders.Get(msg.ParentID); ok {
reply = &_reply
}
}

// Post normal message.
b.Log.Debugf("=> Sending message %#v", msg)
// Generate a dummy ID because to avoid collision with other internal messages
msgID := xid.New().String()
if _, err := b.xc.Send(xmpp.Chat{
Type: "groupchat",
Remote: msg.Channel + "@" + b.GetString("Muc"),
Text: msg.Username + msg.Text,
Type: "groupchat",
Remote: msg.Channel + "@" + b.GetString("Muc"),
Text: msg.Username + msg.Text,
OriginID: msgID,
Reply: reply,
}); err != nil {
return "", err
}

// Generate a dummy ID because to avoid collision with other internal messages
// However this does not provide proper Edits/Replies integration on XMPP side.
msgID := xid.New().String()
return msgID, nil
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently matterbridge insists on knowing a message ID right after bridge.Send. I believe this may be a wrong architecture designed around synchronous operations, but we really want to make matterbridge asynchronous by nature.

I think your interpretation of the codebase is correct and "should work". I'm ok to merge this (after more review etc), but i would also appreciate if we took some time in the coming month to document the reply system entirely and make it fully asynchronous.

}

Expand Down Expand Up @@ -300,6 +322,13 @@ func (b *Bxmpp) handleXMPP() error {
if v.Type == "groupchat" {
b.Log.Debugf("== Receiving %#v", v)

if v.StanzaID.ID != "" {
// Here the stanza-id has been set by the server and can be used to provide replies
// as explained in XEP-0461 https://xmpp.org/extensions/xep-0461.html#business-id
b.stanzaIDs.Add(v.StanzaID.ID, v.ID)
b.replyHeaders.Add(v.ID, xmpp.Reply{ID: v.StanzaID.ID, To: v.Remote})
}
Comment on lines +325 to +330
Copy link
Copy Markdown

@kousu kousu Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want to be noisy in another project's repo so I'm following-up on this here, although the issue crosses both projects.

Why ID and not OriginID

@kousu: origin-id is not set by all clients (Dino, for example)

@selfhoster1312: I don't think this is the case. dino definitely sets origin-id (there's even old issues from 2018 about that feature on their github). And if a client doesn't set origin-id, it just cannot support message replies. message id field is only guaranteed unique on a C2S level… for a message federated with a third party (private message or MUC) only origin-id should be used.

Well I'm not sure about 2018 but Dino's not sending origin-id in 2026:

Dino log
XMPP OUT [test@kousu.ca stream:0x55ef0733e550 thread:0x55ef0656e570 2026-03-06T11:34:59-0500]
<message id='ce3f4b97-aab1-4908-a1fb-3bf3a55ab0df' to='bridge-test@example.im' type='groupchat'>
  <body>
    XMPP Original
  </body>
</message>

XMPP OUT [test@kousu.ca stream:0x55ef0733e550 thread:0x55ef0656e570 2026-03-06T11:34:59-0500]
<r xmlns='urn:xmpp:sm:3' />

XMPP IN [test@kousu.ca stream:0x55ef0733e550 thread:0x55ef0656e570 2026-03-06T11:35:00-0500]
<a h='113' xmlns='urn:xmpp:sm:3' />

XMPP IN [test@kousu.ca stream:0x55ef0733e550 thread:0x55ef0656e570 2026-03-06T11:35:00-0500]
<message type='groupchat' from='bridge-test@example.im/test' lang='en' to='test@kousu.ca/dino.a5466526' id='ce3f4b97-aab1-4908-a1fb-3bf3a55ab0df'>
  <body>
    XMPP Original
  </body>
  <occupant-id id='r6pg036dNqOJs9TanMiowwrEGgZP4nr8ivU+W7pE0Og=' xmlns='urn:xmpp:occupant-id:0' />
  <stanza-id by='bridge-test@example.im' id='2026-03-06-e30b84344d0ead48' xmlns='urn:xmpp:sid:0' />
</message>

I made a branch that uses OriginID. Maybe you could take some time to experiment with it and see why it doesn't work:

cd $(mktemp -d)
git clone --depth 1 -b xmpp-reply-origin-id https://github.com/kousu/matterbridge
cd matterbridge
go build
./matterbridge -version

With that in place these are the logs:

matterbridge log
[0020]  INFO xmpp:         RECV <message type='groupchat' from='bridge-test@example.im/test' xml:lang='en' to='matterbridge@kousu.ca/_AYBJoz1aVnZ' id='ce3f4b97-aab1-4908-a1fb-3bf3a55ab0df'><body>XMPP Original</body><occupant-id id='r6pg036dNqOJs9TanMiowwrEGgZP4nr8ivU+W7pE0Og=' xmlns='urn:xmpp:occupant-id:0'/><stanza-id by='bridge-test@example.im' id='2026-03-06-e30b84344d0ead48' xmlns='urn:xmpp:sid:0'/></message>
[0020] DEBUG xmpp:         [handleXMPP:bridge/xmpp/xmpp.go:323] == Receiving xmpp.Chat{ID:"ce3f4b97-aab1-4908-a1fb-3bf3a55ab0df", Remote:"bridge-test@example.im/test", Type:"groupchat", Text:"XMPP Original", Subject:"", Thread:"", Oob:xmpp.Oob{XMLName:xml.Name{Space:"", Local:""}, Url:"", Desc:""}, Ooburl:"", Oobdesc:"", Lang:"en", StanzaID:xmpp.StanzaID{XMLName:xml.Name{Space:"urn:xmpp:sid:0", Local:"stanza-id"}, Text:"", Xmlns:"urn:xmpp:sid:0", ID:"2026-03-06-e30b84344d0ead48", By:"bridge-test@example.im"}, OriginID:"", Reply:(*xmpp.Reply)(nil), Roster:xmpp.Roster(nil), Other:[]string{""}, OtherElem:[]xmpp.XMLElement{xmpp.XMLElement{XMLName:xml.Name{Space:"urn:xmpp:occupant-id:0", Local:"occupant-id"}, Attr:[]xml.Attr{xml.Attr{Name:xml.Name{Space:"", Local:"id"}, Value:"r6pg036dNqOJs9TanMiowwrEGgZP4nr8ivU+W7pE0Og="}, xml.Attr{Name:xml.Name{Space:"", Local:"xmlns"}, Value:"urn:xmpp:occupant-id:0"}}, InnerXML:""}}, Stamp:time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)}
[0020] DEBUG xmpp:         [handleXMPP:bridge/xmpp/xmpp.go:331] Caching StanzaID: 2026-03-06-e30b84344d0ead48 ->  -> {{ } 2026-03-06-e30b84344d0ead48 bridge-test@example.im/test}

The

r := xmpp.Reply{ID: v.StanzaID.ID, To: v.Remote}
b.replyHeaders.Add(v.OriginID, r)
b.Log.Debugf("Caching StanzaID: %v -> %v -> %v", v.StanzaID.ID, v.OriginID, r)

Caching StanzaID: 2026-03-06-e30b84344d0ead48 -> -> {{ } 2026-03-06-e30b84344d0ead48 bridge-test@example.im/test}

line confirms OriginID is nil.

and the result is that replies don't reply:

Image

is rendered as:

Image

I would love to make only minimal changes to go-xmpp but we tried and found it didn't work 🥲, so this is where we ended up.

We're talking past each other. I'm sure you have an important concern that I'm totally overlooking. Something broad maybe? What is it? Are you worried that id isn't long-term stable? Is it because you prefer to use StanzaID as the canonical ID? Are you worried about putting too much pressure for changes on go-xmpp?

Avoiding ID

I have thought about trying to do this without exposing ID in go-xmpp. It's possible but super awkward and I'm sure it will lead to confusion and bugs.

The task: we need to pick IDs that the gateway can use to refer to each message permanently as soon as the message is sent/received.

The incoming case is easy: set config.Message.ID = xmpp.Chat.StanzaID.ID (which is in fact the actual situation), and then an incoming reply will be tagged with the same StanzaID and can pass it directly up to the gateway as ParentID, so that's great ⭐ .

But the outgoing case is doubly complicated to compensate for the simplicity on that side 😭🧅🧅🧅: it needs to find out the OriginID and/or ID set by go-xmpp (always necessitating some patch to go-xmpp) -- let's say we use OriginID -- and then return that to the gateway as the stable ID. When the echoed copy comes in we need to record it in a outgoingMessageIDs lru.Cache[StanzaID, OriginID] but make sure to detect it was an echo to one of our outgoing messages; how would you do that 1? And then when a reply comes in, we need to check for OriginID , _ := outgoingMessageIDs.Get(StanzaID) and return the OriginID in that case.

So then there's two sets of IDs intermixed in the gateway's cache.

Using .ID everywhere is simpler to reason about.

Using StanzaID

Just to be clear, I am for using StanzaID as the stable ID, but until #159 is done that is really hard because we don't know it until the echo. And that's the kind of patch that can go on for months or years. Please don't let it block XMPP replies; I want to use replies now, and I want to build reactions on it soon.

Having Send() -> xmpp.Chat

mdosch: I think in general it would make sense to return xmpp.Chat as there might also be other stuff generated in the future that might not be of interest today.
I made a POC commit for the Send() function there: xmppo/go-xmpp@1089118

@kousu : I like the idea of xmppo/go-xmpp@1089118 a lot, I can definitely work with that instead

@selfhoster1312 : Please do, as it seems to be the way the go-xmpp maintainer has chosen :)

Having go-xmpp pick the outgoing IDs is cleaner and I will use that, but we still need it to expose .ID because otherwise the code is split-brained between the outgoing and incoming paths for all the reasons above.

🏴‍☠️ 🥇 🤝 🥇 🏴‍☠️

I'm sorry this is dragging on so long 😅 . I don't want to turn this into a lumbering monster. What will it take to move it forward?

Footnotes

  1. Naively: keep a list outstandingIDs []OriginID, so that's one extra data structure to juggle. Or avoid it by examining from == "matterbridge@example.im" but I'm worried that's forgeable; it surely risks breaking in weird ways if multiple bridges ran simultaneously by accident or if someone logged in manually as the bridge for testing. So it's hard to see how to avoid juggling that extra list.


// Skip invalid messages.
if b.skipMessage(v) {
continue
Expand All @@ -320,18 +349,42 @@ func (b *Bxmpp) handleXMPP() error {
avatar = getAvatar(b.avatarMap, v.Remote, b.General)
}

// If there was a <reply>, map the StanzaID to the local matterbridge message ID
// so we can inform the other bridges of this message has a parent
var parentID string
var parentText string
if v.Reply != nil {
if _parentID, ok := b.stanzaIDs.Get(v.Reply.ID); ok {
parentID = _parentID
}
body := v.Text
// Capture quoted lines into parentText so destination bridges can decide
// how they should be displayed.
for strings.HasPrefix(body, "> ") {
lineIdx := strings.IndexRune(body, '\n')
if lineIdx == -1 {
parentText += body[2:]
body = ""
} else {
parentText += body[2:lineIdx] + "\n"
body = body[(lineIdx + 1):]
}
}
parentText = strings.TrimRight(parentText, "\n")
v.Text = body
}

rmsg := config.Message{
Username: b.parseNick(v.Remote),
Text: v.Text,
Channel: b.parseChannel(v.Remote),
Account: b.Account,
Avatar: avatar,
UserID: v.Remote,
// Here the stanza-id has been set by the server and can be used to provide replies
// as explained in XEP-0461 https://xmpp.org/extensions/xep-0461.html#business-id
ID: v.StanzaID.ID,
Event: event,
Extra: make(map[string][]any),
Username: b.parseNick(v.Remote),
Text: v.Text,
Channel: b.parseChannel(v.Remote),
Account: b.Account,
Avatar: avatar,
UserID: v.Remote,
ID: v.ID,
Event: event,
ParentID: parentID,
ParentText: parentText,
}

// Check if we have an action event.
Expand Down
16 changes: 9 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/google/gops v0.3.27
github.com/gorilla/schema v1.4.1
github.com/hashicorp/golang-lru v1.0.2
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/jpillora/backoff v1.0.0
github.com/kyokomi/emoji/v2 v2.2.13
github.com/labstack/echo/v4 v4.12.0
Expand Down Expand Up @@ -44,7 +45,7 @@ require (
go.mau.fi/whatsmeow v0.0.0-20260123132415-83db04703aee
golang.org/x/image v0.19.0
golang.org/x/oauth2 v0.22.0
golang.org/x/text v0.33.0
golang.org/x/text v0.34.0
gomod.garykim.dev/nc-talk v0.3.0
google.golang.org/protobuf v1.36.11
layeh.com/gumble v0.0.0-20221205141517-d1df60a3cc14
Expand Down Expand Up @@ -76,7 +77,6 @@ require (
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.6.1 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/kettek/apng v0.0.0-20191108220231-414630eed80f // indirect
Expand Down Expand Up @@ -133,11 +133,11 @@ require (
go.mau.fi/libsignal v0.2.1 // indirect
go.mau.fi/util v0.9.5 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.47.0 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/term v0.39.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/term v0.40.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade // indirect
google.golang.org/grpc v1.65.0 // indirect
Expand All @@ -156,4 +156,6 @@ require (

//replace github.com/matrix-org/gomatrix => github.com/matterbridge/gomatrix v0.0.0-20220205235239-607eb9ee6419

go 1.24.0
go 1.25.0

replace github.com/xmppo/go-xmpp => github.com/sh4sh/go-xmpp v0.0.0-20260306045944-c36945baa3d9
24 changes: 12 additions & 12 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,8 @@ github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/sh4sh/go-xmpp v0.0.0-20260306045944-c36945baa3d9 h1:i3Wc7UxeqhfoipWxigPqO1JERwd1Dq76zqm9KtgR+Ds=
github.com/sh4sh/go-xmpp v0.0.0-20260306045944-c36945baa3d9/go.mod h1:veSQsIhh/ySAtFYcNwaH+qOTtbJaH3gWOLnlK1f8pRs=
github.com/shazow/rateio v0.0.0-20200113175441-4461efc8bdc4 h1:zwQ1HBo5FYwn1ksMd19qBCKO8JAWE9wmHivEpkw/DvE=
github.com/shazow/rateio v0.0.0-20200113175441-4461efc8bdc4/go.mod h1:vt2jWY/3Qw1bIzle5thrJWucsLuuX9iUNnp20CqCciI=
github.com/shazow/ssh-chat v1.10.1 h1:ePS+ngEYqm+yUuXegDPutysqLV2WoI22XDOeRgI6CE0=
Expand Down Expand Up @@ -419,8 +421,6 @@ github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6Fk
github.com/writeas/go-strip-markdown v2.0.1+incompatible/go.mod h1:Rsyu10ZhbEK9pXdk8V6MVnZmTzRG0alMNLMwa0J01fE=
github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg=
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
github.com/xmppo/go-xmpp v0.3.2 h1:RAQf7yD2XNH1UAy4OPlofnI2qLVx1iQzAR7ghptvaA8=
github.com/xmppo/go-xmpp v0.3.2/go.mod h1:EBLbzPt4Y9OJBEF58Lhc4IrTnO226aIkbfosQo6KWeA=
github.com/yaegashi/msgraph.go v0.1.4 h1:leDXSczAbwBpYFSmmZrdByTiPoUw8dbTfNMetAjJvbw=
github.com/yaegashi/msgraph.go v0.1.4/go.mod h1:vgeYhHa5skJt/3lTyjGXThTZhwbhRnGo6uUxzoJIGME=
github.com/yaegashi/wtz.go v0.0.2/go.mod h1:nOLA5QXsmdkRxBkP5tljhua13ADHCKirLBrzPf4PEJc=
Expand All @@ -447,8 +447,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
Expand Down Expand Up @@ -476,8 +476,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
Expand Down Expand Up @@ -517,20 +517,20 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
Expand Down
Loading