diff --git a/bridge/discord/webhook.go b/bridge/discord/webhook.go index b518ea62..4e647b38 100644 --- a/bridge/discord/webhook.go +++ b/bridge/discord/webhook.go @@ -2,6 +2,7 @@ package bdiscord import ( "bytes" + "strings" "github.com/42wim/matterbridge/bridge/config" "github.com/42wim/matterbridge/bridge/helper" @@ -42,13 +43,65 @@ func (b *Bdiscord) maybeGetLocalAvatar(msg *config.Message) string { return "" } +func (b *Bdiscord) webhookSendTextOnly(msg *config.Message, channelID string) (string, error) { + msgParts := helper.ClipOrSplitMessage(msg.Text, MessageLength, b.GetString("MessageClipped"), b.GetInt("MessageSplitMaxCount")) + var msgIds = []string{} + for _, msgPart := range msgParts { + res, err := b.transmitter.Send( + channelID, + &discordgo.WebhookParams{ + Content: msgPart, + Username: msg.Username, + AvatarURL: msg.Avatar, + AllowedMentions: b.getAllowedMentions(), + }, + ) + if err != nil { + return "", err + } else { + msgIds = append(msgIds, res.ID) + } + } + // Exploit that a discord message ID is actually just a large number, so we encode a list of IDs by separating them with ";". + return strings.Join(msgIds, ";"), nil +} + +func (b *Bdiscord) webhookSendFilesOnly(msg *config.Message, channelID string) error { + for _, f := range msg.Extra["file"] { + fi := f.(config.FileInfo) + file := discordgo.File{ + Name: fi.Name, + ContentType: "", + Reader: bytes.NewReader(*fi.Data), + } + content := fi.Comment + + // Cannot use the resulting ID for any edits anyway, so throw it away. + // This has to be re-enabled when we implement message deletion. + _, err := b.transmitter.Send( + channelID, + &discordgo.WebhookParams{ + Username: msg.Username, + AvatarURL: msg.Avatar, + Files: []*discordgo.File{&file}, + Content: content, + AllowedMentions: b.getAllowedMentions(), + }, + ) + if err != nil { + b.Log.Errorf("Could not send file %#v for message %#v: %s", file, msg, err) + return err + } + } + return nil +} + // webhookSend send one or more message via webhook, taking care of file // uploads (from slack, telegram or mattermost). // Returns messageID and error. -func (b *Bdiscord) webhookSend(msg *config.Message, channelID string) (*discordgo.Message, error) { +func (b *Bdiscord) webhookSend(msg *config.Message, channelID string) (string, error) { var ( - res *discordgo.Message - res2 *discordgo.Message + res string err error ) @@ -61,48 +114,11 @@ func (b *Bdiscord) webhookSend(msg *config.Message, channelID string) (*discordg // We can't send empty messages. if msg.Text != "" { - res, err = b.transmitter.Send( - channelID, - &discordgo.WebhookParams{ - Content: msg.Text, - Username: msg.Username, - AvatarURL: msg.Avatar, - AllowedMentions: b.getAllowedMentions(), - }, - ) - if err != nil { - b.Log.Errorf("Could not send text (%s) for message %#v: %s", msg.Text, msg, err) - } + res, err = b.webhookSendTextOnly(msg, channelID) } - if msg.Extra != nil { - for _, f := range msg.Extra["file"] { - fi := f.(config.FileInfo) - file := discordgo.File{ - Name: fi.Name, - ContentType: "", - Reader: bytes.NewReader(*fi.Data), - } - content := fi.Comment - - res2, err = b.transmitter.Send( - channelID, - &discordgo.WebhookParams{ - Username: msg.Username, - AvatarURL: msg.Avatar, - Files: []*discordgo.File{&file}, - Content: content, - AllowedMentions: b.getAllowedMentions(), - }, - ) - if err != nil { - b.Log.Errorf("Could not send file %#v for message %#v: %s", file, msg, err) - } - } - } - - if msg.Text == "" { - res = res2 + if err == nil && msg.Extra != nil { + err = b.webhookSendFilesOnly(msg, channelID) } return res, err @@ -120,35 +136,44 @@ func (b *Bdiscord) handleEventWebhook(msg *config.Message, channelID string) (st return "", nil } - msg.Text = helper.ClipMessage(msg.Text, MessageLength, b.GetString("MessageClipped")) - msg.Text = b.replaceUserMentions(msg.Text) // discord username must be [0..32] max if len(msg.Username) > 32 { msg.Username = msg.Username[0:32] } if msg.ID != "" { + // Exploit that a discord message ID is actually just a large number, and we encode a list of IDs by separating them with ";". + var msgIds = strings.Split(msg.ID, ";") + msgParts := helper.ClipOrSplitMessage(b.replaceUserMentions(msg.Text), MessageLength, b.GetString("MessageClipped"), len(msgIds)) + for len(msgParts) < len(msgIds) { + msgParts = append(msgParts, "((obsoleted by edit))") + } b.Log.Debugf("Editing webhook message") - err := b.transmitter.Edit(channelID, msg.ID, &discordgo.WebhookParams{ - Content: msg.Text, - Username: msg.Username, - AllowedMentions: b.getAllowedMentions(), - }) - if err == nil { + var edit_err error = nil + for i := range msgParts { + // In case of split-messages where some parts remain the same (i.e. only a typo-fix in a huge message), this causes some noop-updates. + // TODO: Optimize away noop-updates of un-edited messages + edit_err = b.transmitter.Edit(channelID, msgIds[i], &discordgo.WebhookParams{ + Content: msgParts[i], + Username: msg.Username, + AllowedMentions: b.getAllowedMentions(), + }) + if edit_err != nil { + break + } + } + if edit_err == nil { return msg.ID, nil } - b.Log.Errorf("Could not edit webhook message: %s", err) + b.Log.Errorf("Could not edit webhook message(s): %s; sending as new message(s) instead", edit_err) } b.Log.Debugf("Processing webhook sending for message %#v", msg) - discordMsg, err := b.webhookSend(msg, channelID) + msg.Text = b.replaceUserMentions(msg.Text) + msgId, err := b.webhookSend(msg, channelID) if err != nil { - b.Log.Errorf("Could not broadcast via webhook for message %#v: %s", msg, err) + b.Log.Errorf("Could not broadcast via webhook for message %#v: %s", msgId, err) return "", err } - if discordMsg == nil { - return "", nil - } - - return discordMsg.ID, nil + return msgId, nil }