Add support for deleting messages across bridges.

Currently fully support mattermost,slack and discord.
Message deleted on the bridge or received from other bridges will be
deleted.

Partially support for Gitter.
Gitter bridge will delete messages received from other bridges.
But if you delete a message on gitter, this deletion will not be sent to
other bridges (this is a gitter API limitation, it doesn't propogate edits
or deletes via the API)
This commit is contained in:
Wim 2017-09-11 22:45:15 +02:00
parent 90a61f15cc
commit ed01820722
13 changed files with 98 additions and 5 deletions

View File

@ -69,6 +69,10 @@ func (b *Api) JoinChannel(channel config.ChannelInfo) error {
func (b *Api) Send(msg config.Message) (string, error) { func (b *Api) Send(msg config.Message) (string, error) {
b.Lock() b.Lock()
defer b.Unlock() defer b.Unlock()
// ignore delete messages
if msg.Event == config.EVENT_MSG_DELETE {
return "", nil
}
b.Messages.Enqueue(&msg) b.Messages.Enqueue(&msg)
return "", nil return "", nil
} }

View File

@ -14,6 +14,7 @@ const (
EVENT_FAILURE = "failure" EVENT_FAILURE = "failure"
EVENT_REJOIN_CHANNELS = "rejoin_channels" EVENT_REJOIN_CHANNELS = "rejoin_channels"
EVENT_USER_ACTION = "user_action" EVENT_USER_ACTION = "user_action"
EVENT_MSG_DELETE = "msg_delete"
) )
type Message struct { type Message struct {

View File

@ -66,6 +66,7 @@ func (b *bdiscord) Connect() error {
b.c.AddHandler(b.messageCreate) b.c.AddHandler(b.messageCreate)
b.c.AddHandler(b.memberUpdate) b.c.AddHandler(b.memberUpdate)
b.c.AddHandler(b.messageUpdate) b.c.AddHandler(b.messageUpdate)
b.c.AddHandler(b.messageDelete)
err = b.c.Open() err = b.c.Open()
if err != nil { if err != nil {
flog.Debugf("%#v", err) flog.Debugf("%#v", err)
@ -129,6 +130,13 @@ func (b *bdiscord) Send(msg config.Message) (string, error) {
if wID == "" { if wID == "" {
flog.Debugf("Broadcasting using token (API)") flog.Debugf("Broadcasting using token (API)")
if msg.Event == config.EVENT_MSG_DELETE {
if msg.ID == "" {
return "", nil
}
err := b.c.ChannelMessageDelete(channelID, msg.ID)
return "", err
}
if msg.ID != "" { if msg.ID != "" {
_, err := b.c.ChannelMessageEdit(channelID, msg.ID, msg.Username+msg.Text) _, err := b.c.ChannelMessageEdit(channelID, msg.ID, msg.Username+msg.Text)
return msg.ID, err return msg.ID, err
@ -152,6 +160,17 @@ func (b *bdiscord) Send(msg config.Message) (string, error) {
return "", err return "", err
} }
func (b *bdiscord) messageDelete(s *discordgo.Session, m *discordgo.MessageDelete) {
rmsg := config.Message{Account: b.Account, ID: m.ID, Event: config.EVENT_MSG_DELETE, Text: config.EVENT_MSG_DELETE}
rmsg.Channel = b.getChannelName(m.ChannelID)
if b.UseChannelID {
rmsg.Channel = "ID:" + m.ChannelID
}
flog.Debugf("Sending message from %s to gateway", b.Account)
flog.Debugf("Message is %#v", rmsg)
b.Remote <- rmsg
}
func (b *bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) { func (b *bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) {
if b.Config.EditDisable { if b.Config.EditDisable {
return return
@ -223,6 +242,7 @@ func (b *bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
rmsg.Text = text rmsg.Text = text
flog.Debugf("Sending message from %s on %s to gateway", m.Author.Username, b.Account) flog.Debugf("Sending message from %s on %s to gateway", m.Author.Username, b.Account)
flog.Debugf("Message is %#v", rmsg)
b.Remote <- rmsg b.Remote <- rmsg
} }

View File

@ -106,6 +106,17 @@ func (b *Bgitter) Send(msg config.Message) (string, error) {
flog.Errorf("Could not find roomID for %v", msg.Channel) flog.Errorf("Could not find roomID for %v", msg.Channel)
return "", nil return "", nil
} }
if msg.Event == config.EVENT_MSG_DELETE {
if msg.ID == "" {
return "", nil
}
// gitter has no delete message api
_, err := b.c.UpdateMessage(roomID, msg.ID, "")
if err != nil {
return "", err
}
return "", nil
}
if msg.ID != "" { if msg.ID != "" {
flog.Debugf("updating message with id %s", msg.ID) flog.Debugf("updating message with id %s", msg.ID)
_, err := b.c.UpdateMessage(roomID, msg.ID, msg.Username+msg.Text) _, err := b.c.UpdateMessage(roomID, msg.ID, msg.Username+msg.Text)

View File

@ -129,6 +129,10 @@ func (b *Birc) JoinChannel(channel config.ChannelInfo) error {
} }
func (b *Birc) Send(msg config.Message) (string, error) { func (b *Birc) Send(msg config.Message) (string, error) {
// ignore delete messages
if msg.Event == config.EVENT_MSG_DELETE {
return "", nil
}
flog.Debugf("Receiving %#v", msg) flog.Debugf("Receiving %#v", msg)
if strings.HasPrefix(msg.Text, "!") { if strings.HasPrefix(msg.Text, "!") {
b.Command(&msg) b.Command(&msg)

View File

@ -76,6 +76,10 @@ func (b *Bmatrix) JoinChannel(channel config.ChannelInfo) error {
func (b *Bmatrix) Send(msg config.Message) (string, error) { func (b *Bmatrix) Send(msg config.Message) (string, error) {
flog.Debugf("Receiving %#v", msg) flog.Debugf("Receiving %#v", msg)
// ignore delete messages
if msg.Event == config.EVENT_MSG_DELETE {
return "", nil
}
channel := b.getRoomID(msg.Channel) channel := b.getRoomID(msg.Channel)
flog.Debugf("Sending to channel %s", channel) flog.Debugf("Sending to channel %s", channel)
if msg.Event == config.EVENT_USER_ACTION { if msg.Event == config.EVENT_USER_ACTION {

View File

@ -25,6 +25,7 @@ type MMMessage struct {
Username string Username string
UserID string UserID string
ID string ID string
Event string
} }
type Bmattermost struct { type Bmattermost struct {
@ -168,6 +169,12 @@ func (b *Bmattermost) Send(msg config.Message) (string, error) {
} }
return "", nil return "", nil
} }
if msg.Event == config.EVENT_MSG_DELETE {
if msg.ID == "" {
return "", nil
}
return msg.ID, b.mc.DeleteMessage(msg.ID)
}
if msg.ID != "" { if msg.ID != "" {
return b.mc.EditMessage(msg.ID, message) return b.mc.EditMessage(msg.ID, message)
} }
@ -188,7 +195,7 @@ func (b *Bmattermost) handleMatter() {
go b.handleMatterClient(mchan) go b.handleMatterClient(mchan)
} }
for message := range mchan { for message := range mchan {
rmsg := config.Message{Username: message.Username, Channel: message.Channel, Account: b.Account, UserID: message.UserID, ID: message.ID} rmsg := config.Message{Username: message.Username, Channel: message.Channel, Account: b.Account, UserID: message.UserID, ID: message.ID, Event: message.Event}
text, ok := b.replaceAction(message.Text) text, ok := b.replaceAction(message.Text)
if ok { if ok {
rmsg.Event = config.EVENT_USER_ACTION rmsg.Event = config.EVENT_USER_ACTION
@ -215,7 +222,7 @@ func (b *Bmattermost) handleMatterClient(mchan chan *MMMessage) {
} }
// do not post our own messages back to irc // do not post our own messages back to irc
// only listen to message from our team // only listen to message from our team
if (message.Raw.Event == "posted" || message.Raw.Event == "post_edited") && if (message.Raw.Event == "posted" || message.Raw.Event == "post_edited" || message.Raw.Event == "post_deleted") &&
b.mc.User.Username != message.Username && message.Raw.Data["team_id"].(string) == b.TeamId { b.mc.User.Username != message.Username && message.Raw.Data["team_id"].(string) == b.TeamId {
// if the message has reactions don't repost it (for now, until we can correlate reaction with message) // if the message has reactions don't repost it (for now, until we can correlate reaction with message)
if message.Post.HasReactions { if message.Post.HasReactions {
@ -231,6 +238,9 @@ func (b *Bmattermost) handleMatterClient(mchan chan *MMMessage) {
if message.Raw.Event == "post_edited" && !b.Config.EditDisable { if message.Raw.Event == "post_edited" && !b.Config.EditDisable {
m.Text = message.Text + b.Config.EditSuffix m.Text = message.Text + b.Config.EditSuffix
} }
if message.Raw.Event == "post_deleted" {
m.Event = config.EVENT_MSG_DELETE
}
if len(message.Post.FileIds) > 0 { if len(message.Post.FileIds) > 0 {
for _, link := range b.mc.GetFileLinks(message.Post.FileIds) { for _, link := range b.mc.GetFileLinks(message.Post.FileIds) {
m.Text = m.Text + "\n" + link m.Text = m.Text + "\n" + link

View File

@ -58,6 +58,10 @@ func (b *Brocketchat) JoinChannel(channel config.ChannelInfo) error {
} }
func (b *Brocketchat) Send(msg config.Message) (string, error) { func (b *Brocketchat) Send(msg config.Message) (string, error) {
// ignore delete messages
if msg.Event == config.EVENT_MSG_DELETE {
return "", nil
}
flog.Debugf("Receiving %#v", msg) flog.Debugf("Receiving %#v", msg)
matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL} matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL}
matterMessage.Channel = msg.Channel matterMessage.Channel = msg.Channel

View File

@ -166,6 +166,16 @@ func (b *Bslack) Send(msg config.Message) (string, error) {
// replace mentions // replace mentions
np.LinkNames = 1 np.LinkNames = 1
if msg.Event == config.EVENT_MSG_DELETE {
// some protocols echo deletes, but with empty ID
if msg.ID == "" {
return "", nil
}
// we get a "slack <ID>", split it
ts := strings.Fields(msg.ID)
b.sc.DeleteMessage(schannel.ID, ts[1])
return "", nil
}
// if we have no ID it means we're creating a new message, not updating an existing one // if we have no ID it means we're creating a new message, not updating an existing one
if msg.ID != "" { if msg.ID != "" {
ts := strings.Fields(msg.ID) ts := strings.Fields(msg.ID)
@ -231,7 +241,7 @@ func (b *Bslack) handleSlack() {
if b.Config.WebhookURL == "" && b.Config.WebhookBindAddress == "" && message.Username == b.si.User.Name { if b.Config.WebhookURL == "" && b.Config.WebhookBindAddress == "" && message.Username == b.si.User.Name {
continue continue
} }
if message.Text == "" || message.Username == "" { if (message.Text == "" || message.Username == "") && message.Raw.SubType != "message_deleted" {
continue continue
} }
text := message.Text text := message.Text
@ -250,6 +260,12 @@ func (b *Bslack) handleSlack() {
if message.Raw.SubMessage != nil { if message.Raw.SubMessage != nil {
msg.ID = "slack " + message.Raw.SubMessage.Timestamp msg.ID = "slack " + message.Raw.SubMessage.Timestamp
} }
if message.Raw.SubType == "message_deleted" {
msg.Text = config.EVENT_MSG_DELETE
msg.Event = config.EVENT_MSG_DELETE
msg.ID = "slack " + message.Raw.DeletedTimestamp
}
flog.Debugf("Message is %#v", msg)
b.Remote <- msg b.Remote <- msg
} }
} }
@ -276,7 +292,7 @@ func (b *Bslack) handleSlackClient(mchan chan *MMMessage) {
continue continue
} }
m := &MMMessage{} m := &MMMessage{}
if ev.BotID == "" { if ev.BotID == "" && ev.SubType != "message_deleted" {
user, err := b.rtm.GetUserInfo(ev.User) user, err := b.rtm.GetUserInfo(ev.User)
if err != nil { if err != nil {
continue continue

View File

@ -70,6 +70,10 @@ func (b *Bsteam) JoinChannel(channel config.ChannelInfo) error {
} }
func (b *Bsteam) Send(msg config.Message) (string, error) { func (b *Bsteam) Send(msg config.Message) (string, error) {
// ignore delete messages
if msg.Event == config.EVENT_MSG_DELETE {
return "", nil
}
id, err := steamid.NewId(msg.Channel) id, err := steamid.NewId(msg.Channel)
if err != nil { if err != nil {
return "", err return "", err

View File

@ -80,6 +80,10 @@ func (b *Bxmpp) JoinChannel(channel config.ChannelInfo) error {
} }
func (b *Bxmpp) Send(msg config.Message) (string, error) { func (b *Bxmpp) Send(msg config.Message) (string, error) {
// ignore delete messages
if msg.Event == config.EVENT_MSG_DELETE {
return "", nil
}
flog.Debugf("Receiving %#v", msg) flog.Debugf("Receiving %#v", msg)
b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Config.Muc, Text: msg.Username + msg.Text}) b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Config.Muc, Text: msg.Username + msg.Text})
return "", nil return "", nil

View File

@ -1,6 +1,9 @@
# v1.1.2 # v1.1.2
## New features
* general: also build darwin binaries * general: also build darwin binaries
* mattermost: add support for mattermost 4.2.x * mattermost: add support for mattermost 4.2.x
## Bugfix
* mattermost: Send images when text is empty regression. (mattermost). Closes #254 * mattermost: Send images when text is empty regression. (mattermost). Closes #254
* slack: also send the first messsage after connect. #252 * slack: also send the first messsage after connect. #252

View File

@ -299,7 +299,7 @@ func (m *MMClient) WsReceiver() {
func (m *MMClient) parseMessage(rmsg *Message) { func (m *MMClient) parseMessage(rmsg *Message) {
switch rmsg.Raw.Event { switch rmsg.Raw.Event {
case model.WEBSOCKET_EVENT_POSTED, model.WEBSOCKET_EVENT_POST_EDITED: case model.WEBSOCKET_EVENT_POSTED, model.WEBSOCKET_EVENT_POST_EDITED, model.WEBSOCKET_EVENT_POST_DELETED:
m.parseActionPost(rmsg) m.parseActionPost(rmsg)
/* /*
case model.ACTION_USER_REMOVED: case model.ACTION_USER_REMOVED:
@ -476,6 +476,14 @@ func (m *MMClient) EditMessage(postId string, text string) (string, error) {
return res.Id, nil return res.Id, nil
} }
func (m *MMClient) DeleteMessage(postId string) error {
_, resp := m.Client.DeletePost(postId)
if resp.Error != nil {
return resp.Error
}
return nil
}
func (m *MMClient) JoinChannel(channelId string) error { func (m *MMClient) JoinChannel(channelId string) error {
m.RLock() m.RLock()
defer m.RUnlock() defer m.RUnlock()