瀏覽代碼

Implement attachments for Discord -> Matrix

Gary Kramlich 3 年之前
父節點
當前提交
7f99dc4a9e
共有 9 個文件被更改,包括 412 次插入38 次删除
  1. 60 0
      bridge/attachments.go
  2. 176 30
      bridge/portal.go
  3. 70 0
      database/attachment.go
  4. 73 0
      database/attachmentquery.go
  5. 11 5
      database/database.go
  6. 12 0
      database/migrations/02-attachments.sql
  7. 1 0
      database/migrations/migrations.go
  8. 3 3
      go.mod
  9. 6 0
      go.sum

+ 60 - 0
bridge/attachments.go

@@ -0,0 +1,60 @@
+package bridge
+
+import (
+	"bytes"
+	"image"
+	"io/ioutil"
+	"net/http"
+	"strings"
+
+	"github.com/bwmarrin/discordgo"
+
+	"maunium.net/go/mautrix/appservice"
+	"maunium.net/go/mautrix/event"
+)
+
+func (p *Portal) downloadDiscordAttachment(url string) ([]byte, error) {
+	// We might want to make this save to disk in the future. Discord defaults
+	// to 8mb for all attachments to a messages for non-nitro users and
+	// non-boosted servers.
+	//
+	// If the user has nitro classic, their limit goes up to 50mb but if a user
+	// has regular nitro the limit is increased to 100mb.
+	//
+	// Servers boosted to level 2 will have the limit bumped to 50mb.
+
+	req, err := http.NewRequest(http.MethodGet, url, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	req.Header.Set("User-Agent", discordgo.DroidBrowserUserAgent)
+
+	resp, err := http.DefaultClient.Do(req)
+	if err != nil {
+		return nil, err
+	}
+
+	defer resp.Body.Close()
+
+	return ioutil.ReadAll(resp.Body)
+}
+
+func (p *Portal) uploadMatrixAttachment(intent *appservice.IntentAPI, data []byte, content *event.MessageEventContent) error {
+	uploaded, err := intent.UploadBytes(data, content.Info.MimeType)
+	if err != nil {
+		return err
+	}
+
+	content.URL = uploaded.ContentURI.CUString()
+
+	content.Info.Size = len(data)
+
+	if content.Info.Width == 0 && content.Info.Height == 0 && strings.HasPrefix(content.Info.MimeType, "image/") {
+		cfg, _, _ := image.DecodeConfig(bytes.NewReader(data))
+		content.Info.Width = cfg.Width
+		content.Info.Height = cfg.Height
+	}
+
+	return nil
+}

+ 176 - 30
bridge/portal.go

@@ -2,6 +2,7 @@ package bridge
 
 import (
 	"fmt"
+	"strings"
 	"sync"
 	"time"
 
@@ -303,6 +304,79 @@ func (p *Portal) markMessageHandled(msg *database.Message, discordID string, mxi
 	return msg
 }
 
+func (p *Portal) sendMediaFailedMessage(intent *appservice.IntentAPI, bridgeErr error) {
+	content := &event.MessageEventContent{
+		Body:    fmt.Sprintf("Failed to bridge media: %v", bridgeErr),
+		MsgType: event.MsgNotice,
+	}
+
+	_, err := intent.SendMessageEvent(p.MXID, event.EventMessage, content)
+	if err != nil {
+		p.log.Warnfln("failed to send error message to matrix: %v", err)
+	}
+}
+
+func (p *Portal) handleDiscordAttachment(intent *appservice.IntentAPI, msgID string, attachment *discordgo.MessageAttachment) {
+	// var captionContent *event.MessageEventContent
+
+	// if attachment.Description != "" {
+	// 	captionContent = &event.MessageEventContent{
+	// 		Body:    attachment.Description,
+	// 		MsgType: event.MsgNotice,
+	// 	}
+	// }
+	// p.log.Debugfln("captionContent: %#v", captionContent)
+
+	content := &event.MessageEventContent{
+		Body: attachment.Filename,
+		Info: &event.FileInfo{
+			Height:   attachment.Height,
+			MimeType: attachment.ContentType,
+			Width:    attachment.Width,
+
+			// This gets overwritten later after the file is uploaded to the homeserver
+			Size: attachment.Size,
+		},
+	}
+
+	switch strings.ToLower(strings.Split(attachment.ContentType, "/")[0]) {
+	case "audio":
+		content.MsgType = event.MsgAudio
+	case "image":
+		content.MsgType = event.MsgImage
+	case "video":
+		content.MsgType = event.MsgVideo
+	default:
+		content.MsgType = event.MsgFile
+	}
+
+	data, err := p.downloadDiscordAttachment(attachment.URL)
+	if err != nil {
+		p.sendMediaFailedMessage(intent, err)
+
+		return
+	}
+
+	err = p.uploadMatrixAttachment(intent, data, content)
+	if err != nil {
+		p.sendMediaFailedMessage(intent, err)
+
+		return
+	}
+
+	resp, err := intent.SendMessageEvent(p.MXID, event.EventMessage, content)
+	if err != nil {
+		p.log.Warnfln("failed to send media message to matrix: %v", err)
+	}
+
+	dbAttachment := p.bridge.db.Attachment.New()
+	dbAttachment.Channel = p.Key
+	dbAttachment.DiscordMessageID = msgID
+	dbAttachment.DiscordAttachmentID = attachment.ID
+	dbAttachment.MatrixEventID = resp.EventID
+	dbAttachment.Insert()
+}
+
 func (p *Portal) handleDiscordMessageCreate(user *User, msg *discordgo.Message) {
 	if msg.Author != nil && user.ID == msg.Author.ID {
 		return
@@ -321,22 +395,29 @@ func (p *Portal) handleDiscordMessageCreate(user *User, msg *discordgo.Message)
 		return
 	}
 
-	content := &event.MessageEventContent{
-		Body:    msg.Content,
-		MsgType: event.MsgText,
-	}
-
 	intent := p.bridge.GetPuppetByID(msg.Author.ID).IntentFor(p)
 
-	resp, err := intent.SendMessageEvent(p.MXID, event.EventMessage, content)
-	if err != nil {
-		p.log.Warnfln("failed to send message %q to matrix: %v", msg.ID, err)
+	if msg.Content != "" {
+		content := &event.MessageEventContent{
+			Body:    msg.Content,
+			MsgType: event.MsgText,
+		}
 
-		return
+		resp, err := intent.SendMessageEvent(p.MXID, event.EventMessage, content)
+		if err != nil {
+			p.log.Warnfln("failed to send message %q to matrix: %v", msg.ID, err)
+
+			return
+		}
+
+		ts, _ := msg.Timestamp.Parse()
+		p.markMessageHandled(existing, msg.ID, resp.EventID, msg.Author.ID, ts)
 	}
 
-	ts, _ := msg.Timestamp.Parse()
-	p.markMessageHandled(existing, msg.ID, resp.EventID, msg.Author.ID, ts)
+	// now run through any attachments the message has
+	for _, attachment := range msg.Attachments {
+		p.handleDiscordAttachment(intent, msg.ID, attachment)
+	}
 }
 
 func (p *Portal) handleDiscordMessagesUpdate(user *User, msg *discordgo.Message) {
@@ -350,9 +431,45 @@ func (p *Portal) handleDiscordMessagesUpdate(user *User, msg *discordgo.Message)
 		return
 	}
 
+	intent := p.bridge.GetPuppetByID(msg.Author.ID).IntentFor(p)
+
 	existing := p.bridge.db.Message.GetByDiscordID(p.Key, msg.ID)
 	if existing == nil {
-		p.log.Debugln("failed to find previous message to update", msg.ID)
+		// Due to the differences in Discord and Matrix attachment handling,
+		// existing will return nil if the original message was empty as we
+		// don't store/save those messages so we can determine when we're
+		// working against an attachment and do the attachment lookup instead.
+
+		// Find all the existing attachments and drop them in a map so we can
+		// figure out which, if any have been deleted and clean them up on the
+		// matrix side.
+		attachmentMap := map[string]*database.Attachment{}
+		attachments := p.bridge.db.Attachment.GetAllByDiscordMessageID(p.Key, msg.ID)
+
+		for _, attachment := range attachments {
+			attachmentMap[attachment.DiscordAttachmentID] = attachment
+		}
+
+		// Now run through the list of attachments on this message and remove
+		// them from the map.
+		for _, attachment := range msg.Attachments {
+			if _, found := attachmentMap[attachment.ID]; found {
+				delete(attachmentMap, attachment.ID)
+			}
+		}
+
+		// Finally run through any attachments still in the map and delete them
+		// on the matrix side and our database.
+		for _, attachment := range attachmentMap {
+			_, err := intent.RedactEvent(p.MXID, attachment.MatrixEventID)
+			if err != nil {
+				p.log.Warnfln("Failed to remove attachment %s: %v", attachment.MatrixEventID, err)
+			}
+
+			attachment.Delete()
+		}
+
+		return
 	}
 
 	content := &event.MessageEventContent{
@@ -362,8 +479,6 @@ func (p *Portal) handleDiscordMessagesUpdate(user *User, msg *discordgo.Message)
 
 	content.SetEdit(existing.MatrixID)
 
-	intent := p.bridge.GetPuppetByID(msg.Author.ID).IntentFor(p)
-
 	_, err := intent.SendMessageEvent(p.MXID, event.EventMessage, content)
 	if err != nil {
 		p.log.Warnfln("failed to send message %q to matrix: %v", msg.ID, err)
@@ -384,13 +499,9 @@ func (p *Portal) handleDiscordMessageDelete(user *User, msg *discordgo.Message)
 	// add guild message support, but we'll cross that bridge when we get
 	// there.
 
-	// Find the message that we're working with.
+	// Find the message that we're working with. This could correctly return
+	// nil if the message was just one or more attachments.
 	existing := p.bridge.db.Message.GetByDiscordID(p.Key, msg.ID)
-	if existing == nil {
-		p.log.Debugfln("failed to find message", msg.ID)
-
-		return
-	}
 
 	var intent *appservice.IntentAPI
 
@@ -400,12 +511,25 @@ func (p *Portal) handleDiscordMessageDelete(user *User, msg *discordgo.Message)
 		p.log.Errorfln("no guilds yet...")
 	}
 
-	_, err := intent.RedactEvent(p.MXID, existing.MatrixID)
-	if err != nil {
-		p.log.Warnfln("Failed to remove message %s: %v", existing.MatrixID, err)
+	if existing != nil {
+		_, err := intent.RedactEvent(p.MXID, existing.MatrixID)
+		if err != nil {
+			p.log.Warnfln("Failed to remove message %s: %v", existing.MatrixID, err)
+		}
+
+		existing.Delete()
 	}
 
-	existing.Delete()
+	// Now delete all of the existing attachments.
+	attachments := p.bridge.db.Attachment.GetAllByDiscordMessageID(p.Key, msg.ID)
+	for _, attachment := range attachments {
+		_, err := intent.RedactEvent(p.MXID, attachment.MatrixEventID)
+		if err != nil {
+			p.log.Warnfln("Failed to remove attachment %s: %v", attachment.MatrixEventID, err)
+		}
+
+		attachment.Delete()
+	}
 }
 
 func (p *Portal) syncParticipants(source *User, participants []*discordgo.User) {
@@ -615,16 +739,38 @@ func (p *Portal) handleMatrixReaction(evt *event.Event) {
 		return
 	}
 
+	var discordID string
+
 	msg := p.bridge.db.Message.GetByMatrixID(p.Key, reaction.RelatesTo.EventID)
-	if msg.DiscordID == "" {
-		p.log.Debugf("Message %s has not yet been sent to discord", reaction.RelatesTo.EventID)
 
-		return
+	// Due to the differences in attachments between Discord and Matrix, if a
+	// user reacts to a media message on discord our lookup above will fail
+	// because the relation of matrix media messages to attachments in handled
+	// in the attachments table instead of messages so we need to check that
+	// before continuing.
+	//
+	// This also leads to interesting problems when a Discord message comes in
+	// with multiple attachments. A user can react to each one individually on
+	// Matrix, which will cause us to send it twice. Discord tends to ignore
+	// this, but if the user removes one of them, discord removes it and now
+	// they're out of sync. Perhaps we should add a counter to the reactions
+	// table to keep them in sync and to avoid sending duplicates to Discord.
+	if msg == nil {
+		attachment := p.bridge.db.Attachment.GetByMatrixID(p.Key, reaction.RelatesTo.EventID)
+		discordID = attachment.DiscordMessageID
+	} else {
+		if msg.DiscordID == "" {
+			p.log.Debugf("Message %s has not yet been sent to discord", reaction.RelatesTo.EventID)
+
+			return
+		}
+
+		discordID = msg.DiscordID
 	}
 
-	err := user.Session.MessageReactionAdd(p.Key.ChannelID, msg.DiscordID, reaction.RelatesTo.Key)
+	err := user.Session.MessageReactionAdd(p.Key.ChannelID, discordID, reaction.RelatesTo.Key)
 	if err != nil {
-		p.log.Debugf("Failed to send reaction %s@%s: %v", p.Key, msg.DiscordID, err)
+		p.log.Debugf("Failed to send reaction %s@%s: %v", p.Key, discordID, err)
 
 		return
 	}
@@ -633,7 +779,7 @@ func (p *Portal) handleMatrixReaction(evt *event.Event) {
 	dbReaction.Channel.ChannelID = p.Key.ChannelID
 	dbReaction.Channel.Receiver = p.Key.Receiver
 	dbReaction.MatrixEventID = evt.ID
-	dbReaction.DiscordMessageID = msg.DiscordID
+	dbReaction.DiscordMessageID = discordID
 	dbReaction.AuthorID = user.ID
 	dbReaction.MatrixName = reaction.RelatesTo.Key
 	dbReaction.DiscordID = reaction.RelatesTo.Key

+ 70 - 0
database/attachment.go

@@ -0,0 +1,70 @@
+package database
+
+import (
+	"database/sql"
+	"errors"
+
+	log "maunium.net/go/maulogger/v2"
+	"maunium.net/go/mautrix/id"
+)
+
+type Attachment struct {
+	db  *Database
+	log log.Logger
+
+	Channel PortalKey
+
+	DiscordMessageID    string
+	DiscordAttachmentID string
+	MatrixEventID       id.EventID
+}
+
+func (a *Attachment) Scan(row Scannable) *Attachment {
+	err := row.Scan(
+		&a.Channel.ChannelID, &a.Channel.Receiver,
+		&a.DiscordMessageID, &a.DiscordAttachmentID,
+		&a.MatrixEventID)
+
+	if err != nil {
+		if !errors.Is(err, sql.ErrNoRows) {
+			a.log.Errorln("Database scan failed:", err)
+		}
+
+		return nil
+	}
+
+	return a
+}
+
+func (a *Attachment) Insert() {
+	query := "INSERT INTO attachment" +
+		" (channel_id, receiver, discord_message_id, discord_attachment_id, " +
+		" matrix_event_id) VALUES ($1, $2, $3, $4, $5);"
+
+	_, err := a.db.Exec(
+		query,
+		a.Channel.ChannelID, a.Channel.Receiver,
+		a.DiscordMessageID, a.DiscordAttachmentID,
+		a.MatrixEventID,
+	)
+
+	if err != nil {
+		a.log.Warnfln("Failed to insert attachment for %s@%s: %v", a.Channel, a.DiscordMessageID, err)
+	}
+}
+
+func (a *Attachment) Delete() {
+	query := "DELETE FROM attachment WHERE" +
+		" channel_id=$1 AND receiver=$2 AND discord_attachment_id=$3 AND" +
+		" matrix_event_id=$4"
+
+	_, err := a.db.Exec(
+		query,
+		a.Channel.ChannelID, a.Channel.Receiver,
+		a.DiscordAttachmentID, a.MatrixEventID,
+	)
+
+	if err != nil {
+		a.log.Warnfln("Failed to delete attachment for %s@%s: %v", a.Channel, a.DiscordAttachmentID, err)
+	}
+}

+ 73 - 0
database/attachmentquery.go

@@ -0,0 +1,73 @@
+package database
+
+import (
+	log "maunium.net/go/maulogger/v2"
+	"maunium.net/go/mautrix/id"
+)
+
+type AttachmentQuery struct {
+	db  *Database
+	log log.Logger
+}
+
+const (
+	attachmentSelect = "SELECT channel_id, receiver, discord_message_id," +
+		" discord_attachment_id, matrix_event_id FROM attachment"
+)
+
+func (aq *AttachmentQuery) New() *Attachment {
+	return &Attachment{
+		db:  aq.db,
+		log: aq.log,
+	}
+}
+
+func (aq *AttachmentQuery) GetAllByDiscordMessageID(key PortalKey, discordMessageID string) []*Attachment {
+	query := attachmentSelect + " WHERE channel_id=$1 AND receiver=$2 AND" +
+		" discord_message_id=$3"
+
+	return aq.getAll(query, key.ChannelID, key.Receiver, discordMessageID)
+}
+
+func (aq *AttachmentQuery) getAll(query string, args ...interface{}) []*Attachment {
+	rows, err := aq.db.Query(query, args...)
+	if err != nil {
+		aq.log.Debugfln("getAll failed: %v", err)
+
+		return nil
+	}
+
+	if rows == nil {
+		return nil
+	}
+
+	attachments := []*Attachment{}
+	for rows.Next() {
+		attachments = append(attachments, aq.New().Scan(rows))
+	}
+
+	return attachments
+}
+
+func (aq *AttachmentQuery) GetByDiscordAttachmentID(key PortalKey, discordMessageID, discordID string) *Attachment {
+	query := attachmentSelect + " WHERE channel_id=$1 AND receiver=$2" +
+		" AND discord_message_id=$3 AND discord_id=$4"
+
+	return aq.get(query, key.ChannelID, key.Receiver, discordMessageID, discordID)
+}
+
+func (aq *AttachmentQuery) GetByMatrixID(key PortalKey, matrixEventID id.EventID) *Attachment {
+	query := attachmentSelect + " WHERE channel_id=$1 AND receiver=$2" +
+		" AND matrix_event_id=$3"
+
+	return aq.get(query, key.ChannelID, key.Receiver, matrixEventID)
+}
+
+func (aq *AttachmentQuery) get(query string, args ...interface{}) *Attachment {
+	row := aq.db.QueryRow(query, args...)
+	if row == nil {
+		return nil
+	}
+
+	return aq.New().Scan(row)
+}

+ 11 - 5
database/database.go

@@ -16,11 +16,12 @@ type Database struct {
 	log     log.Logger
 	dialect string
 
-	User     *UserQuery
-	Portal   *PortalQuery
-	Puppet   *PuppetQuery
-	Message  *MessageQuery
-	Reaction *ReactionQuery
+	User       *UserQuery
+	Portal     *PortalQuery
+	Puppet     *PuppetQuery
+	Message    *MessageQuery
+	Reaction   *ReactionQuery
+	Attachment *AttachmentQuery
 }
 
 func New(dbType, uri string, maxOpenConns, maxIdleConns int, baseLog log.Logger) (*Database, error) {
@@ -73,5 +74,10 @@ func New(dbType, uri string, maxOpenConns, maxIdleConns int, baseLog log.Logger)
 		log: db.log.Sub("Reaction"),
 	}
 
+	db.Attachment = &AttachmentQuery{
+		db:  db,
+		log: db.log.Sub("Attachment"),
+	}
+
 	return db, nil
 }

+ 12 - 0
database/migrations/02-attachments.sql

@@ -0,0 +1,12 @@
+CREATE TABLE attachment (
+	channel_id TEXT NOT NULL,
+	receiver TEXT NOT NULL,
+
+	discord_message_id TEXT NOT NULL,
+	discord_attachment_id TEXT NOT NULL,
+
+	matrix_event_id TEXT NOT NULL UNIQUE,
+
+	PRIMARY KEY(discord_attachment_id, matrix_event_id),
+	FOREIGN KEY(channel_id, receiver) REFERENCES portal(channel_id, receiver) ON DELETE CASCADE
+);

+ 1 - 0
database/migrations/migrations.go

@@ -40,6 +40,7 @@ func Run(db *sql.DB, baseLog log.Logger) error {
 		migrator.WithLogger(logger),
 		migrator.Migrations(
 			migrationFromFile("01-initial.sql"),
+			migrationFromFile("02-attachments.sql"),
 		),
 	)
 	if err != nil {

+ 3 - 3
go.mod

@@ -6,7 +6,7 @@ require (
 	github.com/alecthomas/kong v0.2.18
 	github.com/bwmarrin/discordgo v0.23.2
 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
-	github.com/gorilla/websocket v1.4.2
+	github.com/gorilla/websocket v1.5.0
 	github.com/lib/pq v1.9.0
 	github.com/lopezator/migrator v0.3.0
 	github.com/mattn/go-sqlite3 v1.14.10
@@ -21,9 +21,9 @@ require (
 	github.com/gorilla/mux v1.8.0 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
-	golang.org/x/crypto v0.0.0-20220209195652-db638375bc3a // indirect
+	golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect
 	golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
 	golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
 )
 
-replace github.com/bwmarrin/discordgo v0.23.2 => gitlab.com/beeper/discordgo v0.23.3-0.20220210113317-784a5c1cfaa2
+replace github.com/bwmarrin/discordgo v0.23.2 => gitlab.com/beeper/discordgo v0.23.3-0.20220219094025-13ff4cc63da7

+ 6 - 0
go.sum

@@ -26,6 +26,8 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7
 github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
+github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
@@ -57,6 +59,8 @@ github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhso
 github.com/tidwall/sjson v1.2.3/go.mod h1:5WdjKx3AQMvCJ4RG6/2UYT7dLrGvJUV1x4jdTAyGvZs=
 gitlab.com/beeper/discordgo v0.23.3-0.20220210113317-784a5c1cfaa2 h1:CK9faDZlCY4rbxpqPArNdMy1kOsIrVHDEAVJcgarnrg=
 gitlab.com/beeper/discordgo v0.23.3-0.20220210113317-784a5c1cfaa2/go.mod h1:Hwfv4M8yP/MDh47BN+4Z1WItJ1umLKUyplCH5KcQPgE=
+gitlab.com/beeper/discordgo v0.23.3-0.20220219094025-13ff4cc63da7 h1:8ieR27GadHnShqhsvPrDzL1/ZOntavGGt4TXqafncYE=
+gitlab.com/beeper/discordgo v0.23.3-0.20220219094025-13ff4cc63da7/go.mod h1:Hwfv4M8yP/MDh47BN+4Z1WItJ1umLKUyplCH5KcQPgE=
 golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -64,6 +68,8 @@ golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220209195652-db638375bc3a h1:atOEWVSedO4ksXBe/UrlbSLVxQQ9RxM/tT2Jy10IaHo=
 golang.org/x/crypto v0.0.0-20220209195652-db638375bc3a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
+golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=