5
0
mirror of https://github.com/cwinfo/matterbridge.git synced 2024-11-10 03:10:26 +00:00

Add support for editing messages across bridges. Currently mattermost/discord.

Our Message type has an extra ID field which contains the message ID of the specific bridge.
The Send() function has been modified to return a msg ID (after the message to that specific
bridge has been created).

There is a lru cache of 5000 entries (message IDs). All in memory, so editing messages
will only work for messages the bot has seen.

Currently we go out from the idea that every message ID is unique, so we don't keep
the ID separate for each bridge. (we do for each gateway though)

If there's a new message from a bridge, we put that message ID in the LRU cache as key
and the []*BrMsgID as value (this slice contains the message ID's of each bridge that
received the new message)

If there's a new message and this message ID already exists in the cache, it must be
an updated message. The value from the cache gets checked for each bridge and if there
is a message ID for this bridge, the ID will be added to the Message{} sent to that
bridge. If the bridge sees that the ID isn't empty, it'll know it has to update the
message with that specific ID instead of creating a new message.
This commit is contained in:
Wim 2017-08-28 00:33:17 +02:00
parent e84417430d
commit 7c773ebae0
5 changed files with 61 additions and 24 deletions

View File

@ -27,6 +27,7 @@ type Message struct {
Protocol string `json:"protocol"` Protocol string `json:"protocol"`
Gateway string `json:"gateway"` Gateway string `json:"gateway"`
Timestamp time.Time `json:"timestamp"` Timestamp time.Time `json:"timestamp"`
ID string `json:"id"`
} }
type ChannelInfo struct { type ChannelInfo struct {

View File

@ -129,20 +129,27 @@ 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)")
b.c.ChannelMessageSend(channelID, msg.Username+msg.Text) if msg.ID != "" {
} else { _, err := b.c.ChannelMessageEdit(channelID, msg.ID, msg.Username+msg.Text)
flog.Debugf("Broadcasting using Webhook") return msg.ID, err
b.c.WebhookExecute( }
wID, res, err := b.c.ChannelMessageSend(channelID, msg.Username+msg.Text)
wToken, if err != nil {
true, return "", err
&discordgo.WebhookParams{ }
Content: msg.Text, return res.ID, err
Username: msg.Username,
AvatarURL: msg.Avatar,
})
} }
return "", nil flog.Debugf("Broadcasting using Webhook")
err := b.c.WebhookExecute(
wID,
wToken,
true,
&discordgo.WebhookParams{
Content: msg.Text,
Username: msg.Username,
AvatarURL: msg.Avatar,
})
return "", err
} }
func (b *bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) { func (b *bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) {
@ -185,7 +192,7 @@ func (b *bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
} }
rmsg := config.Message{Account: b.Account, Avatar: "https://cdn.discordapp.com/avatars/" + m.Author.ID + "/" + m.Author.Avatar + ".jpg", rmsg := config.Message{Account: b.Account, Avatar: "https://cdn.discordapp.com/avatars/" + m.Author.ID + "/" + m.Author.Avatar + ".jpg",
UserID: m.Author.ID} UserID: m.Author.ID, ID: m.ID}
rmsg.Channel = b.getChannelName(m.ChannelID) rmsg.Channel = b.getChannelName(m.ChannelID)
if b.UseChannelID { if b.UseChannelID {

View File

@ -23,6 +23,7 @@ type MMMessage struct {
Channel string Channel string
Username string Username string
UserID string UserID string
ID string
} }
type Bmattermost struct { type Bmattermost struct {
@ -162,8 +163,10 @@ func (b *Bmattermost) Send(msg config.Message) (string, error) {
} }
return "", nil return "", nil
} }
b.mc.PostMessage(b.mc.GetChannelId(channel, ""), message) if msg.ID != "" {
return "", nil return b.mc.EditMessage(msg.ID, message)
}
return b.mc.PostMessage(b.mc.GetChannelId(channel, ""), message)
} }
func (b *Bmattermost) handleMatter() { func (b *Bmattermost) handleMatter() {
@ -180,7 +183,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} rmsg := config.Message{Username: message.Username, Channel: message.Channel, Account: b.Account, UserID: message.UserID, ID: message.ID}
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
@ -218,6 +221,7 @@ func (b *Bmattermost) handleMatterClient(mchan chan *MMMessage) {
m.Username = message.Username m.Username = message.Username
m.Channel = message.Channel m.Channel = message.Channel
m.Text = message.Text m.Text = message.Text
m.ID = message.Post.Id
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
} }

View File

@ -6,6 +6,7 @@ import (
"github.com/42wim/matterbridge/bridge/config" "github.com/42wim/matterbridge/bridge/config"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
// "github.com/davecgh/go-spew/spew" // "github.com/davecgh/go-spew/spew"
"github.com/hashicorp/golang-lru"
"github.com/peterhellberg/emojilib" "github.com/peterhellberg/emojilib"
"regexp" "regexp"
"strings" "strings"
@ -21,10 +22,12 @@ type Gateway struct {
ChannelOptions map[string]config.ChannelOptions ChannelOptions map[string]config.ChannelOptions
Message chan config.Message Message chan config.Message
Name string Name string
Messages map[string][]*BridgeMsg Messages *lru.Cache
//map[string][]*BrMsg
lruCache *lru.Cache
} }
type BridgeMsg struct { type BrMsgID struct {
br *bridge.Bridge br *bridge.Bridge
ID string ID string
} }
@ -32,6 +35,8 @@ type BridgeMsg struct {
func New(cfg config.Gateway, r *Router) *Gateway { func New(cfg config.Gateway, r *Router) *Gateway {
gw := &Gateway{Channels: make(map[string]*config.ChannelInfo), Message: r.Message, gw := &Gateway{Channels: make(map[string]*config.ChannelInfo), Message: r.Message,
Router: r, Bridges: make(map[string]*bridge.Bridge), Config: r.Config} Router: r, Bridges: make(map[string]*bridge.Bridge), Config: r.Config}
cache, _ := lru.New(5000)
gw.Messages = cache
gw.AddConfig(&cfg) gw.AddConfig(&cfg)
return gw return gw
} }
@ -142,15 +147,16 @@ func (gw *Gateway) getDestChannel(msg *config.Message, dest bridge.Bridge) []con
return channels return channels
} }
func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) { func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrMsgID {
var brMsgIDs []*BrMsgID
// only relay join/part when configged // only relay join/part when configged
if msg.Event == config.EVENT_JOIN_LEAVE && !gw.Bridges[dest.Account].Config.ShowJoinPart { if msg.Event == config.EVENT_JOIN_LEAVE && !gw.Bridges[dest.Account].Config.ShowJoinPart {
return return brMsgIDs
} }
// broadcast to every out channel (irc QUIT) // broadcast to every out channel (irc QUIT)
if msg.Channel == "" && msg.Event != config.EVENT_JOIN_LEAVE { if msg.Channel == "" && msg.Event != config.EVENT_JOIN_LEAVE {
log.Debug("empty channel") log.Debug("empty channel")
return return brMsgIDs
} }
originchannel := msg.Channel originchannel := msg.Channel
origmsg := msg origmsg := msg
@ -164,15 +170,28 @@ func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) {
msg.Channel = channel.Name msg.Channel = channel.Name
msg.Avatar = gw.modifyAvatar(origmsg, dest) msg.Avatar = gw.modifyAvatar(origmsg, dest)
msg.Username = gw.modifyUsername(origmsg, dest) msg.Username = gw.modifyUsername(origmsg, dest)
msg.ID = ""
if res, ok := gw.Messages.Get(origmsg.ID); ok {
IDs := res.([]*BrMsgID)
for _, id := range IDs {
if dest.Protocol == id.br.Protocol {
msg.ID = id.ID
}
}
}
// for api we need originchannel as channel // for api we need originchannel as channel
if dest.Protocol == "api" { if dest.Protocol == "api" {
msg.Channel = originchannel msg.Channel = originchannel
} }
_, err := dest.Send(msg) mID, err := dest.Send(msg)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
} }
// append the message ID (mID) from this bridge (dest) to our brMsgIDs slice
log.Debugf("message ID: %s\n", mID)
brMsgIDs = append(brMsgIDs, &BrMsgID{dest, mID})
} }
return brMsgIDs
} }
func (gw *Gateway) ignoreMessage(msg *config.Message) bool { func (gw *Gateway) ignoreMessage(msg *config.Message) bool {

View File

@ -94,11 +94,17 @@ func (r *Router) handleReceive() {
} }
} }
for _, gw := range r.Gateways { for _, gw := range r.Gateways {
// record all the message ID's of the different bridges
var msgIDs []*BrMsgID
if !gw.ignoreMessage(&msg) { if !gw.ignoreMessage(&msg) {
msg.Timestamp = time.Now() msg.Timestamp = time.Now()
gw.modifyMessage(&msg) gw.modifyMessage(&msg)
for _, br := range gw.Bridges { for _, br := range gw.Bridges {
gw.handleMessage(msg, br) msgIDs = append(msgIDs, gw.handleMessage(msg, br)...)
}
// only add the message ID if it doesn't already exists
if _, ok := gw.Messages.Get(msg.ID); !ok {
gw.Messages.Add(msg.ID, msgIDs)
} }
} }
} }