diff --git a/bridge/config/config.go b/bridge/config/config.go index 7b7c2f2e..16b397f3 100644 --- a/bridge/config/config.go +++ b/bridge/config/config.go @@ -23,6 +23,7 @@ const ( EventRejoinChannels = "rejoin_channels" EventUserAction = "user_action" EventMsgDelete = "msg_delete" + EventFileDelete = "file_delete" EventAPIConnected = "api_connected" EventUserTyping = "user_typing" EventGetChannelMembers = "get_channel_members" @@ -56,13 +57,14 @@ func (m Message) ParentValid() bool { } type FileInfo struct { - Name string - Data *[]byte - Comment string - URL string - Size int64 - Avatar bool - SHA string + Name string + Data *[]byte + Comment string + URL string + Size int64 + Avatar bool + SHA string + NativeID string } type ChannelInfo struct { diff --git a/bridge/discord/discord.go b/bridge/discord/discord.go index 0ac2b50c..9b9065f8 100644 --- a/bridge/discord/discord.go +++ b/bridge/discord/discord.go @@ -10,10 +10,14 @@ import ( "github.com/42wim/matterbridge/bridge/config" "github.com/42wim/matterbridge/bridge/discord/transmitter" "github.com/42wim/matterbridge/bridge/helper" + lru "github.com/hashicorp/golang-lru" "github.com/matterbridge/discordgo" ) -const MessageLength = 1950 +const ( + MessageLength = 1950 + cFileUpload = "file_upload" +) type Bdiscord struct { *bridge.Config @@ -35,10 +39,20 @@ type Bdiscord struct { // Webhook specific logic useAutoWebhooks bool transmitter *transmitter.Transmitter + cache *lru.Cache } func New(cfg *bridge.Config) bridge.Bridger { - b := &Bdiscord{Config: cfg} + newCache, err := lru.New(5000) + if err != nil { + cfg.Log.Fatalf("Could not create LRU cache: %v", err) + } + + b := &Bdiscord{ + Config: cfg, + cache: newCache, + } + b.userMemberMap = make(map[string]*discordgo.Member) b.nickMemberMap = make(map[string]*discordgo.Member) b.channelInfoMap = make(map[string]*config.ChannelInfo) @@ -280,6 +294,21 @@ func (b *Bdiscord) handleEventBotUser(msg *config.Message, channelID string) (st return "", err } + // Delete a file + if msg.Event == config.EventFileDelete { + if msg.ID == "" { + return "", nil + } + + if fi, ok := b.cache.Get(cFileUpload + msg.ID); ok { + err := b.c.ChannelMessageDelete(channelID, fi.(string)) // nolint:forcetypeassert + b.cache.Remove(cFileUpload + msg.ID) + return "", err + } + + return "", fmt.Errorf("file %s not found", msg.ID) + } + // Upload a file if it exists if msg.Extra != nil { for _, rmsg := range helper.HandleExtra(msg, b.General) { @@ -327,7 +356,6 @@ func (b *Bdiscord) handleEventBotUser(msg *config.Message, channelID string) (st // handleUploadFile handles native upload of files func (b *Bdiscord) handleUploadFile(msg *config.Message, channelID string) (string, error) { - var err error for _, f := range msg.Extra["file"] { fi := f.(config.FileInfo) file := discordgo.File{ @@ -340,10 +368,15 @@ func (b *Bdiscord) handleUploadFile(msg *config.Message, channelID string) (stri Files: []*discordgo.File{&file}, AllowedMentions: b.getAllowedMentions(), } - _, err = b.c.ChannelMessageSendComplex(channelID, &m) + res, err := b.c.ChannelMessageSendComplex(channelID, &m) if err != nil { return "", fmt.Errorf("file upload failed: %s", err) } + + // link file_upload_nativeID (file ID from the original bridge) to our upload id + // so that we can remove this later when it eg needs to be deleted + b.cache.Add(cFileUpload+fi.NativeID, res.ID) } + return "", nil } diff --git a/bridge/helper/helper.go b/bridge/helper/helper.go index 581a2c43..fb65d8d9 100644 --- a/bridge/helper/helper.go +++ b/bridge/helper/helper.go @@ -168,17 +168,23 @@ func HandleDownloadSize(logger *logrus.Entry, msg *config.Message, name string, // HandleDownloadData adds the data for a remote file into a Matterbridge gateway message. func HandleDownloadData(logger *logrus.Entry, msg *config.Message, name, comment, url string, data *[]byte, general *config.Protocol) { + HandleDownloadData2(logger, msg, name, "", comment, url, data, general) +} + +// HandleDownloadData adds the data for a remote file into a Matterbridge gateway message. +func HandleDownloadData2(logger *logrus.Entry, msg *config.Message, name, id, comment, url string, data *[]byte, general *config.Protocol) { var avatar bool logger.Debugf("Download OK %#v %#v", name, len(*data)) if msg.Event == config.EventAvatarDownload { avatar = true } msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{ - Name: name, - Data: data, - URL: url, - Comment: comment, - Avatar: avatar, + Name: name, + Data: data, + URL: url, + Comment: comment, + Avatar: avatar, + NativeID: id, }) } diff --git a/bridge/slack/handlers.go b/bridge/slack/handlers.go index 4dc57d0d..c469b9a5 100644 --- a/bridge/slack/handlers.go +++ b/bridge/slack/handlers.go @@ -27,7 +27,8 @@ func (b *Bslack) handleSlack() { b.Log.Debug("Start listening for Slack messages") for message := range messages { // don't do any action on deleted/typing messages - if message.Event != config.EventUserTyping && message.Event != config.EventMsgDelete { + if message.Event != config.EventUserTyping && message.Event != config.EventMsgDelete && + message.Event != config.EventFileDelete { b.Log.Debugf("<= Sending message from %s on %s to gateway", message.Username, b.Account) // cleanup the message message.Text = b.replaceMention(message.Text) @@ -76,6 +77,13 @@ func (b *Bslack) handleSlackClient(messages chan *config.Message) { continue } messages <- rmsg + case *slack.FileDeletedEvent: + rmsg, err := b.handleFileDeletedEvent(ev) + if err != nil { + b.Log.Errorf("%#v", err) + continue + } + messages <- rmsg case *slack.OutgoingErrorEvent: b.Log.Debugf("%#v", ev.Error()) case *slack.ChannelJoinedEvent: @@ -222,6 +230,26 @@ func (b *Bslack) handleMessageEvent(ev *slack.MessageEvent) (*config.Message, er return rmsg, nil } +func (b *Bslack) handleFileDeletedEvent(ev *slack.FileDeletedEvent) (*config.Message, error) { + if rawChannel, ok := b.cache.Get(cfileDownloadChannel + ev.FileID); ok { + channel, err := b.channels.getChannelByID(rawChannel.(string)) + if err != nil { + return nil, err + } + + return &config.Message{ + Event: config.EventFileDelete, + Text: config.EventFileDelete, + Channel: channel.Name, + Account: b.Account, + ID: ev.FileID, + Protocol: b.Protocol, + }, nil + } + + return nil, fmt.Errorf("channel ID for file ID %s not found", ev.FileID) +} + func (b *Bslack) handleStatusEvent(ev *slack.MessageEvent, rmsg *config.Message) bool { switch ev.SubType { case sChannelJoined, sMemberJoined: @@ -281,6 +309,8 @@ func (b *Bslack) handleAttachments(ev *slack.MessageEvent, rmsg *config.Message) // If we have files attached, download them (in memory) and put a pointer to it in msg.Extra. for i := range ev.Files { + // keep reference in cache on which channel we added this file + b.cache.Add(cfileDownloadChannel+ev.Files[i].ID, ev.Channel) if err := b.handleDownloadFile(rmsg, &ev.Files[i], false); err != nil { b.Log.Errorf("Could not download incoming file: %#v", err) } @@ -330,7 +360,7 @@ func (b *Bslack) handleDownloadFile(rmsg *config.Message, file *slack.File, retr // that the comment is not duplicated. comment := rmsg.Text rmsg.Text = "" - helper.HandleDownloadData(b.Log, rmsg, file.Name, comment, file.URLPrivateDownload, data, b.General) + helper.HandleDownloadData2(b.Log, rmsg, file.Name, file.ID, comment, file.URLPrivateDownload, data, b.General) return nil } diff --git a/bridge/slack/slack.go b/bridge/slack/slack.go index 5db0c33e..55231c38 100644 --- a/bridge/slack/slack.go +++ b/bridge/slack/slack.go @@ -36,24 +36,25 @@ type Bslack struct { } const ( - sHello = "hello" - sChannelJoin = "channel_join" - sChannelLeave = "channel_leave" - sChannelJoined = "channel_joined" - sMemberJoined = "member_joined_channel" - sMessageChanged = "message_changed" - sMessageDeleted = "message_deleted" - sSlackAttachment = "slack_attachment" - sPinnedItem = "pinned_item" - sUnpinnedItem = "unpinned_item" - sChannelTopic = "channel_topic" - sChannelPurpose = "channel_purpose" - sFileComment = "file_comment" - sMeMessage = "me_message" - sUserTyping = "user_typing" - sLatencyReport = "latency_report" - sSystemUser = "system" - sSlackBotUser = "slackbot" + sHello = "hello" + sChannelJoin = "channel_join" + sChannelLeave = "channel_leave" + sChannelJoined = "channel_joined" + sMemberJoined = "member_joined_channel" + sMessageChanged = "message_changed" + sMessageDeleted = "message_deleted" + sSlackAttachment = "slack_attachment" + sPinnedItem = "pinned_item" + sUnpinnedItem = "unpinned_item" + sChannelTopic = "channel_topic" + sChannelPurpose = "channel_purpose" + sFileComment = "file_comment" + sMeMessage = "me_message" + sUserTyping = "user_typing" + sLatencyReport = "latency_report" + sSystemUser = "system" + sSlackBotUser = "slackbot" + cfileDownloadChannel = "file_download_channel" tokenConfig = "Token" incomingWebhookConfig = "WebhookBindAddress" diff --git a/gateway/gateway.go b/gateway/gateway.go index 85f5a187..fc75916c 100644 --- a/gateway/gateway.go +++ b/gateway/gateway.go @@ -447,7 +447,10 @@ func (gw *Gateway) SendMessage( msg.Avatar = gw.modifyAvatar(rmsg, dest) msg.Username = gw.modifyUsername(rmsg, dest) - msg.ID = gw.getDestMsgID(rmsg.Protocol+" "+rmsg.ID, dest, channel) + // exclude file delete event as the msg ID here is the native file ID that needs to be deleted + if msg.Event != config.EventFileDelete { + msg.ID = gw.getDestMsgID(rmsg.Protocol+" "+rmsg.ID, dest, channel) + } // for api we need originchannel as channel if dest.Protocol == apiProtocol {