|
@@ -1,6 +1,7 @@
|
|
package main
|
|
package main
|
|
|
|
|
|
import (
|
|
import (
|
|
|
|
+ "bytes"
|
|
"errors"
|
|
"errors"
|
|
"fmt"
|
|
"fmt"
|
|
"reflect"
|
|
"reflect"
|
|
@@ -41,6 +42,8 @@ type portalMatrixMessage struct {
|
|
user *User
|
|
user *User
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+var relayClient, _ = discordgo.New("")
|
|
|
|
+
|
|
type Portal struct {
|
|
type Portal struct {
|
|
*database.Portal
|
|
*database.Portal
|
|
|
|
|
|
@@ -85,7 +88,7 @@ func (portal *Portal) MarkEncrypted() {
|
|
}
|
|
}
|
|
|
|
|
|
func (portal *Portal) ReceiveMatrixEvent(user bridge.User, evt *event.Event) {
|
|
func (portal *Portal) ReceiveMatrixEvent(user bridge.User, evt *event.Event) {
|
|
- if user.GetPermissionLevel() >= bridgeconfig.PermissionLevelUser /*|| portal.HasRelaybot()*/ {
|
|
|
|
|
|
+ if user.GetPermissionLevel() >= bridgeconfig.PermissionLevelUser || portal.RelayWebhookID != "" {
|
|
portal.matrixMessages <- portalMatrixMessage{user: user.(*User), evt: evt}
|
|
portal.matrixMessages <- portalMatrixMessage{user: user.(*User), evt: evt}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -971,6 +974,7 @@ var (
|
|
errUnknownRelationType = errors.New("unknown relation type")
|
|
errUnknownRelationType = errors.New("unknown relation type")
|
|
errTargetNotFound = errors.New("target event not found")
|
|
errTargetNotFound = errors.New("target event not found")
|
|
errUnknownEmoji = errors.New("unknown emoji")
|
|
errUnknownEmoji = errors.New("unknown emoji")
|
|
|
|
+ errCantStartThread = errors.New("can't create thread without being logged into Discord")
|
|
)
|
|
)
|
|
|
|
|
|
func errorToStatusReason(err error) (reason event.MessageStatusReason, status event.MessageStatus, isCertain, sendNotice bool, humanMessage string) {
|
|
func errorToStatusReason(err error) (reason event.MessageStatusReason, status event.MessageStatus, isCertain, sendNotice bool, humanMessage string) {
|
|
@@ -981,7 +985,8 @@ func errorToStatusReason(err error) (reason event.MessageStatusReason, status ev
|
|
errors.Is(err, errUnknownEmoji),
|
|
errors.Is(err, errUnknownEmoji),
|
|
errors.Is(err, id.InvalidContentURI),
|
|
errors.Is(err, id.InvalidContentURI),
|
|
errors.Is(err, attachment.UnsupportedVersion),
|
|
errors.Is(err, attachment.UnsupportedVersion),
|
|
- errors.Is(err, attachment.UnsupportedAlgorithm):
|
|
|
|
|
|
+ errors.Is(err, attachment.UnsupportedAlgorithm),
|
|
|
|
+ errors.Is(err, errCantStartThread):
|
|
return event.MessageStatusUnsupported, event.MessageStatusFail, true, true, ""
|
|
return event.MessageStatusUnsupported, event.MessageStatusFail, true, true, ""
|
|
case errors.Is(err, attachment.HashMismatch),
|
|
case errors.Is(err, attachment.HashMismatch),
|
|
errors.Is(err, attachment.InvalidKey),
|
|
errors.Is(err, attachment.InvalidKey),
|
|
@@ -1065,6 +1070,22 @@ func (portal *Portal) sendMessageMetrics(evt *event.Event, err error, part strin
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+func (portal *Portal) getRelayUserMeta(sender *User) (name, avatarURL string) {
|
|
|
|
+ member := portal.bridge.StateStore.GetMember(portal.MXID, sender.MXID)
|
|
|
|
+ name = member.Displayname
|
|
|
|
+ if name == "" {
|
|
|
|
+ name = sender.MXID.String()
|
|
|
|
+ }
|
|
|
|
+ mxc := member.AvatarURL.ParseOrIgnore()
|
|
|
|
+ if !mxc.IsEmpty() {
|
|
|
|
+ avatarURL = mautrix.BuildURL(
|
|
|
|
+ portal.bridge.PublicHSAddress,
|
|
|
|
+ "_matrix", "media", "v3", "download", mxc.Homeserver, mxc.FileID,
|
|
|
|
+ ).String()
|
|
|
|
+ }
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
|
|
func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
|
|
if portal.IsPrivateChat() && sender.DiscordID != portal.Key.Receiver {
|
|
if portal.IsPrivateChat() && sender.DiscordID != portal.Key.Receiver {
|
|
go portal.sendMessageMetrics(evt, errUserNotReceiver, "Ignoring")
|
|
go portal.sendMessageMetrics(evt, errUserNotReceiver, "Ignoring")
|
|
@@ -1078,14 +1099,23 @@ func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
|
|
}
|
|
}
|
|
|
|
|
|
channelID := portal.Key.ChannelID
|
|
channelID := portal.Key.ChannelID
|
|
|
|
+ sess := sender.Session
|
|
var threadID string
|
|
var threadID string
|
|
|
|
|
|
if editMXID := content.GetRelatesTo().GetReplaceID(); editMXID != "" && content.NewContent != nil {
|
|
if editMXID := content.GetRelatesTo().GetReplaceID(); editMXID != "" && content.NewContent != nil {
|
|
edits := portal.bridge.DB.Message.GetByMXID(portal.Key, editMXID)
|
|
edits := portal.bridge.DB.Message.GetByMXID(portal.Key, editMXID)
|
|
if edits != nil {
|
|
if edits != nil {
|
|
- discordContent := portal.parseMatrixHTML(content.NewContent)
|
|
|
|
- // TODO save edit in message table
|
|
|
|
- _, err := sender.Session.ChannelMessageEdit(edits.DiscordProtoChannelID(), edits.DiscordID, discordContent)
|
|
|
|
|
|
+ discordContent, allowedMentions := portal.parseMatrixHTML(content.NewContent)
|
|
|
|
+ var err error
|
|
|
|
+ if sess != nil {
|
|
|
|
+ // TODO save edit in message table
|
|
|
|
+ _, err = sess.ChannelMessageEdit(edits.DiscordProtoChannelID(), edits.DiscordID, discordContent)
|
|
|
|
+ } else {
|
|
|
|
+ _, err = relayClient.WebhookMessageEdit(portal.RelayWebhookID, portal.RelayWebhookSecret, edits.DiscordID, &discordgo.WebhookEdit{
|
|
|
|
+ Content: &discordContent,
|
|
|
|
+ AllowedMentions: allowedMentions,
|
|
|
|
+ })
|
|
|
|
+ }
|
|
go portal.sendMessageMetrics(evt, err, "Failed to edit")
|
|
go portal.sendMessageMetrics(evt, err, "Failed to edit")
|
|
} else {
|
|
} else {
|
|
go portal.sendMessageMetrics(evt, fmt.Errorf("%w %s", errUnknownEditTarget, editMXID), "Ignoring")
|
|
go portal.sendMessageMetrics(evt, fmt.Errorf("%w %s", errUnknownEditTarget, editMXID), "Ignoring")
|
|
@@ -1096,6 +1126,11 @@ func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
|
|
if existingThread != nil {
|
|
if existingThread != nil {
|
|
threadID = existingThread.ID
|
|
threadID = existingThread.ID
|
|
} else {
|
|
} else {
|
|
|
|
+ if sess == nil {
|
|
|
|
+ // TODO start thread with bot?
|
|
|
|
+ go portal.sendMessageMetrics(evt, errCantStartThread, "Dropping")
|
|
|
|
+ return
|
|
|
|
+ }
|
|
var err error
|
|
var err error
|
|
threadID, err = portal.startThreadFromMatrix(sender, threadRoot)
|
|
threadID, err = portal.startThreadFromMatrix(sender, threadRoot)
|
|
if err != nil {
|
|
if err != nil {
|
|
@@ -1129,48 +1164,85 @@ func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- sendReq.Content = portal.parseMatrixHTML(content)
|
|
|
|
|
|
+ sendReq.Content, sendReq.AllowedMentions = portal.parseMatrixHTML(content)
|
|
case event.MsgAudio, event.MsgFile, event.MsgImage, event.MsgVideo:
|
|
case event.MsgAudio, event.MsgFile, event.MsgImage, event.MsgVideo:
|
|
data, err := downloadMatrixAttachment(portal.MainIntent(), content)
|
|
data, err := downloadMatrixAttachment(portal.MainIntent(), content)
|
|
if err != nil {
|
|
if err != nil {
|
|
go portal.sendMessageMetrics(evt, err, "Error downloading media in")
|
|
go portal.sendMessageMetrics(evt, err, "Error downloading media in")
|
|
return
|
|
return
|
|
}
|
|
}
|
|
-
|
|
|
|
- att := &discordgo.MessageAttachment{
|
|
|
|
- ID: "0",
|
|
|
|
- Filename: content.Body,
|
|
|
|
- Description: description,
|
|
|
|
- }
|
|
|
|
- sendReq.Attachments = []*discordgo.MessageAttachment{att}
|
|
|
|
|
|
+ filename := content.Body
|
|
if content.FileName != "" && content.FileName != content.Body {
|
|
if content.FileName != "" && content.FileName != content.Body {
|
|
- att.Filename = content.FileName
|
|
|
|
- sendReq.Content = portal.parseMatrixHTML(content)
|
|
|
|
- }
|
|
|
|
- prep, err := sender.Session.ChannelAttachmentCreate(channelID, &discordgo.ReqPrepareAttachments{
|
|
|
|
- Files: []*discordgo.FilePrepare{{
|
|
|
|
- Size: len(data),
|
|
|
|
- Name: att.Filename,
|
|
|
|
- ID: sender.NextDiscordUploadID(),
|
|
|
|
- }},
|
|
|
|
- })
|
|
|
|
- if err != nil {
|
|
|
|
- go portal.sendMessageMetrics(evt, err, "Error preparing to reupload media in")
|
|
|
|
- return
|
|
|
|
|
|
+ filename = content.FileName
|
|
|
|
+ sendReq.Content, sendReq.AllowedMentions = portal.parseMatrixHTML(content)
|
|
}
|
|
}
|
|
- prepared := prep.Attachments[0]
|
|
|
|
- att.UploadedFilename = prepared.UploadFilename
|
|
|
|
- err = uploadDiscordAttachment(prepared.UploadURL, data)
|
|
|
|
- if err != nil {
|
|
|
|
- go portal.sendMessageMetrics(evt, err, "Error reuploading media in")
|
|
|
|
- return
|
|
|
|
|
|
+
|
|
|
|
+ if sess != nil && sess.IsUser {
|
|
|
|
+ att := &discordgo.MessageAttachment{
|
|
|
|
+ ID: "0",
|
|
|
|
+ Filename: filename,
|
|
|
|
+ Description: description,
|
|
|
|
+ }
|
|
|
|
+ sendReq.Attachments = []*discordgo.MessageAttachment{att}
|
|
|
|
+ prep, err := sender.Session.ChannelAttachmentCreate(channelID, &discordgo.ReqPrepareAttachments{
|
|
|
|
+ Files: []*discordgo.FilePrepare{{
|
|
|
|
+ Size: len(data),
|
|
|
|
+ Name: att.Filename,
|
|
|
|
+ ID: sender.NextDiscordUploadID(),
|
|
|
|
+ }},
|
|
|
|
+ })
|
|
|
|
+ if err != nil {
|
|
|
|
+ go portal.sendMessageMetrics(evt, err, "Error preparing to reupload media in")
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ prepared := prep.Attachments[0]
|
|
|
|
+ att.UploadedFilename = prepared.UploadFilename
|
|
|
|
+ err = uploadDiscordAttachment(prepared.UploadURL, data)
|
|
|
|
+ if err != nil {
|
|
|
|
+ go portal.sendMessageMetrics(evt, err, "Error reuploading media in")
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ sendReq.Files = []*discordgo.File{{
|
|
|
|
+ Name: filename,
|
|
|
|
+ ContentType: content.Info.MimeType,
|
|
|
|
+ Reader: bytes.NewReader(data),
|
|
|
|
+ }}
|
|
}
|
|
}
|
|
default:
|
|
default:
|
|
go portal.sendMessageMetrics(evt, fmt.Errorf("%w %q", errUnknownMsgType, content.MsgType), "Ignoring")
|
|
go portal.sendMessageMetrics(evt, fmt.Errorf("%w %q", errUnknownMsgType, content.MsgType), "Ignoring")
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
+ if sess != nil {
|
|
|
|
+ // AllowedMentions must not be set for real users, and it's also not that useful for personal bots.
|
|
|
|
+ // It's only important for relaying, where the webhook may have higher permissions than the user on Matrix.
|
|
|
|
+ sendReq.AllowedMentions = nil
|
|
|
|
+ } else if strings.Contains(sendReq.Content, "@everyone") || strings.Contains(sendReq.Content, "@here") {
|
|
|
|
+ powerLevels, err := portal.MainIntent().PowerLevels(portal.MXID)
|
|
|
|
+ if err != nil {
|
|
|
|
+ portal.log.Warnfln("Failed to get power levels in %s to check if %s can @everyone: %v", portal.MXID, sender.MXID, err)
|
|
|
|
+ } else if powerLevels.GetUserLevel(sender.MXID) >= powerLevels.Notifications.Room() {
|
|
|
|
+ sendReq.AllowedMentions.Parse = append(sendReq.AllowedMentions.Parse, discordgo.AllowedMentionTypeEveryone)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
sendReq.Nonce = generateNonce()
|
|
sendReq.Nonce = generateNonce()
|
|
- msg, err := sender.Session.ChannelMessageSendComplex(channelID, &sendReq)
|
|
|
|
|
|
+ var msg *discordgo.Message
|
|
|
|
+ var err error
|
|
|
|
+ if sess != nil {
|
|
|
|
+ msg, err = sess.ChannelMessageSendComplex(channelID, &sendReq)
|
|
|
|
+ } else {
|
|
|
|
+ username, avatarURL := portal.getRelayUserMeta(sender)
|
|
|
|
+ msg, err = relayClient.WebhookThreadExecute(portal.RelayWebhookID, portal.RelayWebhookSecret, true, threadID, &discordgo.WebhookParams{
|
|
|
|
+ Content: sendReq.Content,
|
|
|
|
+ Username: username,
|
|
|
|
+ AvatarURL: avatarURL,
|
|
|
|
+ TTS: sendReq.TTS,
|
|
|
|
+ Files: sendReq.Files,
|
|
|
|
+ Components: sendReq.Components,
|
|
|
|
+ Embeds: sendReq.Embeds,
|
|
|
|
+ AllowedMentions: sendReq.AllowedMentions,
|
|
|
|
+ })
|
|
|
|
+ }
|
|
go portal.sendMessageMetrics(evt, err, "Error sending")
|
|
go portal.sendMessageMetrics(evt, err, "Error sending")
|
|
if msg != nil {
|
|
if msg != nil {
|
|
dbMsg := portal.bridge.DB.Message.New()
|
|
dbMsg := portal.bridge.DB.Message.New()
|
|
@@ -1180,7 +1252,11 @@ func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) {
|
|
dbMsg.AttachmentID = msg.Attachments[0].ID
|
|
dbMsg.AttachmentID = msg.Attachments[0].ID
|
|
}
|
|
}
|
|
dbMsg.MXID = evt.ID
|
|
dbMsg.MXID = evt.ID
|
|
- dbMsg.SenderID = sender.DiscordID
|
|
|
|
|
|
+ if sess != nil {
|
|
|
|
+ dbMsg.SenderID = sender.DiscordID
|
|
|
|
+ } else {
|
|
|
|
+ dbMsg.SenderID = portal.RelayWebhookID
|
|
|
|
+ }
|
|
dbMsg.Timestamp, _ = discordgo.SnowflakeTimestamp(msg.ID)
|
|
dbMsg.Timestamp, _ = discordgo.SnowflakeTimestamp(msg.ID)
|
|
dbMsg.ThreadID = threadID
|
|
dbMsg.ThreadID = threadID
|
|
dbMsg.Insert()
|
|
dbMsg.Insert()
|
|
@@ -1333,6 +1409,9 @@ func (portal *Portal) handleMatrixReaction(sender *User, evt *event.Event) {
|
|
if portal.IsPrivateChat() && sender.DiscordID != portal.Key.Receiver {
|
|
if portal.IsPrivateChat() && sender.DiscordID != portal.Key.Receiver {
|
|
go portal.sendMessageMetrics(evt, errUserNotReceiver, "Ignoring")
|
|
go portal.sendMessageMetrics(evt, errUserNotReceiver, "Ignoring")
|
|
return
|
|
return
|
|
|
|
+ } else if !sender.IsLoggedIn() {
|
|
|
|
+ //go portal.sendMessageMetrics(evt, errReactionUserNotLoggedIn, "Ignoring")
|
|
|
|
+ return
|
|
}
|
|
}
|
|
|
|
|
|
reaction := evt.Content.AsReaction()
|
|
reaction := evt.Content.AsReaction()
|