Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make replay a service in preparation for consolidating with search & scrollback #554

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 172 additions & 0 deletions mm-go-irckit/service.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package irckit

import (
"encoding/binary"
"errors"
"fmt"
"sort"
Expand All @@ -9,6 +10,8 @@ import (
"time"
"unicode"

bolt "go.etcd.io/bbolt"

"github.com/42wim/matterircd/bridge"
"github.com/mattermost/mattermost-server/v6/model"
)
Expand Down Expand Up @@ -209,6 +212,174 @@ func login(u *User, toUser *User, args []string, service string) {
u.MsgUser(toUser, "login OK")
}

func createSpoof(u *User, mmchannel *bridge.ChannelInfo) func(string, string, ...int) {
if strings.Contains(mmchannel.Name, "__") {
return func(nick string, msg string, maxlen ...int) {
if usr, ok := u.Srv.HasUser(nick); ok {
u.MsgSpoofUser(usr, u.Nick, msg)
} else {
logger.Errorf("%s not found for replay msg", nick)
}
}
}

channelName := mmchannel.Name

if mmchannel.TeamID != u.br.GetMe().TeamID || u.v.GetBool(u.br.Protocol()+".prefixmainteam") {
channelName = u.br.GetTeamName(mmchannel.TeamID) + "/" + mmchannel.Name
}

u.syncChannel(mmchannel.ID, "#"+channelName)
ch := u.Srv.Channel(mmchannel.ID)

return ch.SpoofMessage
}

//nolint:funlen,gocognit,gocyclo,cyclop
func replay(u *User, toUser *User, args []string, service string) {
if len(args) == 0 || len(args) > 2 {
u.MsgUser(toUser, "need REPLAY (#<channel>|<user>)")
u.MsgUser(toUser, "e.g. REPLAY #bugs")
return
}

channelName := strings.TrimPrefix(args[0], "#")
channelTeamID := u.br.GetMe().TeamID
if len(args) == 2 {
channelTeamID = args[1]
}
channelID := u.br.GetChannelID(channelName, channelTeamID)
brchannel, err := u.br.GetChannel(channelID)
if err != nil {
logger.Errorf("%s not found", channelName)
return
}

// exclude direct messages
spoof := createSpoof(u, brchannel)

since := u.br.GetLastViewedAt(brchannel.ID)
// ignore invalid/deleted/old channels
if since == 0 {
return
}

logSince := "server"
channame := brchannel.Name
if !brchannel.DM {
channame = fmt.Sprintf("#%s", brchannel.Name)
}

// We used to stored last viewed at if present.
var lastViewedAt int64
key := brchannel.ID
err = u.lastViewedAtDB.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(u.User))
if v := b.Get([]byte(key)); v != nil {
lastViewedAt = int64(binary.LittleEndian.Uint64(v))
}
return nil
})
if err != nil {
logger.Errorf("something wrong with u.lastViewedAtDB.View for %s for channel %s (%s)", u.Nick, channame, brchannel.ID)
lastViewedAt = since
}

// But only use the stored last viewed if it's later than what the server knows.
if lastViewedAt > since {
since = lastViewedAt + 1
logSince = "stored"
}

// post everything to the channel you haven't seen yet
postlist := u.br.GetPostsSince(brchannel.ID, since)
if postlist == nil {
// if the channel is not from the primary team id, we can't get posts
if brchannel.TeamID == u.br.GetMe().TeamID {
logger.Errorf("something wrong with getPostsSince for %s for channel %s (%s)", u.Nick, channame, brchannel.ID)
}
return
}

showReplayHdr := true

mmPostList, _ := postlist.(*model.PostList)
if mmPostList == nil {
return
}
// traverse the order in reverse
for i := len(mmPostList.Order) - 1; i >= 0; i-- {
p := mmPostList.Posts[mmPostList.Order[i]]
if p.Type == model.PostTypeJoinLeave {
continue
}

if p.DeleteAt > p.CreateAt {
continue
}

// GetPostsSince will return older messages with reaction
// changes since LastViewedAt. This will be confusing as
// the user will think it's a duplicate, or a post out of
// order. Plus, we don't show reaction changes when
// relaying messages/logs so let's skip these.
if p.CreateAt < since {
continue
}

ts := time.Unix(0, p.CreateAt*int64(time.Millisecond))

props := p.GetProps()
botname, override := props["override_username"].(string)
user := u.br.GetUser(p.UserId)
nick := user.Nick
if override {
nick = botname
}

if p.Type == model.PostTypeAddToTeam || p.Type == model.PostTypeRemoveFromTeam {
nick = systemUser
}

for _, post := range strings.Split(p.Message, "\n") {
if showReplayHdr {
date := ts.Format("2006-01-02 15:04:05")
if brchannel.DM {
spoof(nick, fmt.Sprintf("\x02Replaying msgs since %s\x0f (%s)", date, logSince))
} else {
spoof("matterircd", fmt.Sprintf("\x02Replaying msgs since %s\x0f (%s)", date, logSince))
}
logger.Infof("Replaying msgs for %s for %s (%s) since %s (%s)", u.Nick, channame, brchannel.ID, date, logSince)
showReplayHdr = false
}

if nick == systemUser {
post = "\x1d" + post + "\x1d"
}

replayMsg := fmt.Sprintf("[%s] %s", ts.Format("15:04"), post)
if (u.v.GetBool(u.br.Protocol()+".prefixcontext") || u.v.GetBool(u.br.Protocol()+".suffixcontext")) && nick != systemUser {
threadMsgID := u.prefixContext(brchannel.ID, p.Id, p.RootId, "replay")
replayMsg = u.formatContextMessage(ts.Format("15:04"), threadMsgID, post)
}
spoof(nick, replayMsg)
}

if len(p.FileIds) == 0 {
continue
}

for _, fname := range u.br.GetFileLinks(p.FileIds) {
fileMsg := "\x1ddownload file - " + fname + "\x1d" //nolint:goconst
if u.v.GetBool(u.br.Protocol()+".prefixcontext") || u.v.GetBool(u.br.Protocol()+".suffixcontext") {
threadMsgID := u.prefixContext(brchannel.ID, p.Id, p.RootId, "replay_file")
fileMsg = u.formatContextMessage(ts.Format("15:04"), threadMsgID, fileMsg)
}
spoof(nick, fileMsg)
}
}
}

//nolint:cyclop
func search(u *User, toUser *User, args []string, service string) {
if service == "slack" {
Expand Down Expand Up @@ -514,6 +685,7 @@ var cmds = map[string]Command{
"lastsent": {handler: lastsent, login: true, minParams: 0, maxParams: 0},
"logout": {handler: logout, login: true, minParams: 0, maxParams: 0},
"login": {handler: login, minParams: 2, maxParams: 5},
"replay": {handler: replay, login: true, minParams: 1, maxParams: 2},
"part": {handler: part, login: true, minParams: 1, maxParams: 1},
"search": {handler: search, login: true, minParams: 1, maxParams: -1},
"searchusers": {handler: searchUsers, login: true, minParams: 1, maxParams: -1},
Expand Down
156 changes: 5 additions & 151 deletions mm-go-irckit/userbridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -621,165 +621,19 @@ func (u *User) addUsersToChannels() {
go u.handleEventChan()
}

func (u *User) createSpoof(mmchannel *bridge.ChannelInfo) func(string, string, ...int) {
if strings.Contains(mmchannel.Name, "__") {
return func(nick string, msg string, maxlen ...int) {
if usr, ok := u.Srv.HasUser(nick); ok {
u.MsgSpoofUser(usr, u.Nick, msg)
} else {
logger.Errorf("%s not found for replay msg", nick)
}
}
}

channelName := mmchannel.Name

if mmchannel.TeamID != u.br.GetMe().TeamID || u.v.GetBool(u.br.Protocol()+".prefixmainteam") {
channelName = u.br.GetTeamName(mmchannel.TeamID) + "/" + mmchannel.Name
}

u.syncChannel(mmchannel.ID, "#"+channelName)
ch := u.Srv.Channel(mmchannel.ID)

return ch.SpoofMessage
}

//nolint:funlen,gocognit,gocyclo,cyclop
func (u *User) addUserToChannelWorker(channels <-chan *bridge.ChannelInfo, throttle *time.Ticker) {
for brchannel := range channels {
logger.Debug("addUserToChannelWorker", brchannel)

<-throttle.C
// exclude direct messages
spoof := u.createSpoof(brchannel)

since := u.br.GetLastViewedAt(brchannel.ID)
// ignore invalid/deleted/old channels
if since == 0 {
continue
}

logSince := "server"
channame := brchannel.Name
if !brchannel.DM {
channame = fmt.Sprintf("#%s", brchannel.Name)
}
args := []string{brchannel.Name, brchannel.TeamID}
replay(u, u, args, "")

// We used to stored last viewed at if present.
var lastViewedAt int64
key := brchannel.ID
err := u.lastViewedAtDB.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(u.User))
if v := b.Get([]byte(key)); v != nil {
lastViewedAt = int64(binary.LittleEndian.Uint64(v))
}
return nil
})
if err != nil {
logger.Errorf("something wrong with u.lastViewedAtDB.View for %s for channel %s (%s)", u.Nick, channame, brchannel.ID)
lastViewedAt = since
}

// But only use the stored last viewed if it's later than what the server knows.
if lastViewedAt > since {
since = lastViewedAt + 1
logSince = "stored"
}

// post everything to the channel you haven't seen yet
postlist := u.br.GetPostsSince(brchannel.ID, since)
if postlist == nil {
// if the channel is not from the primary team id, we can't get posts
if brchannel.TeamID == u.br.GetMe().TeamID {
logger.Errorf("something wrong with getPostsSince for %s for channel %s (%s)", u.Nick, channame, brchannel.ID)
}
continue
}

showReplayHdr := true

mmPostList, _ := postlist.(*model.PostList)
if mmPostList == nil {
continue
}
// traverse the order in reverse
for i := len(mmPostList.Order) - 1; i >= 0; i-- {
p := mmPostList.Posts[mmPostList.Order[i]]
if p.Type == model.PostTypeJoinLeave {
continue
}

if p.DeleteAt > p.CreateAt {
continue
}

// GetPostsSince will return older messages with reaction
// changes since LastViewedAt. This will be confusing as
// the user will think it's a duplicate, or a post out of
// order. Plus, we don't show reaction changes when
// relaying messages/logs so let's skip these.
if p.CreateAt < since {
continue
}

ts := time.Unix(0, p.CreateAt*int64(time.Millisecond))

props := p.GetProps()
botname, override := props["override_username"].(string)
user := u.br.GetUser(p.UserId)
nick := user.Nick
if override {
nick = botname
}

if p.Type == model.PostTypeAddToTeam || p.Type == model.PostTypeRemoveFromTeam {
nick = systemUser
}

for _, post := range strings.Split(p.Message, "\n") {
if showReplayHdr {
date := ts.Format("2006-01-02 15:04:05")
if brchannel.DM {
spoof(nick, fmt.Sprintf("\x02Replaying msgs since %s\x0f", date))
} else {
spoof("matterircd", fmt.Sprintf("\x02Replaying msgs since %s\x0f", date))
}
logger.Infof("Replaying msgs for %s for %s (%s) since %s (%s)", u.Nick, channame, brchannel.ID, date, logSince)
showReplayHdr = false
}

if nick == systemUser {
post = "\x1d" + post + "\x1d"
}

replayMsg := fmt.Sprintf("[%s] %s", ts.Format("15:04"), post)
if (u.v.GetBool(u.br.Protocol()+".prefixcontext") || u.v.GetBool(u.br.Protocol()+".suffixcontext")) && nick != systemUser {
threadMsgID := u.prefixContext(brchannel.ID, p.Id, p.RootId, "replay")
replayMsg = u.formatContextMessage(ts.Format("15:04"), threadMsgID, post)
}
spoof(nick, replayMsg)
}

if len(p.FileIds) == 0 {
continue
}

for _, fname := range u.br.GetFileLinks(p.FileIds) {
fileMsg := "\x1ddownload file - " + fname + "\x1d"
if u.v.GetBool(u.br.Protocol()+".prefixcontext") || u.v.GetBool(u.br.Protocol()+".suffixcontext") {
threadMsgID := u.prefixContext(brchannel.ID, p.Id, p.RootId, "replay_file")
fileMsg = u.formatContextMessage(ts.Format("15:04"), threadMsgID, fileMsg)
}
spoof(nick, fileMsg)
}
}

if len(mmPostList.Order) > 0 {
if !u.v.GetBool(u.br.Protocol() + ".disableautoview") {
u.updateLastViewed(brchannel.ID)
}
u.saveLastViewedAt(brchannel.ID)
if !u.v.GetBool(u.br.Protocol() + ".disableautoview") {
u.updateLastViewed(brchannel.ID)
}
u.saveLastViewedAt(brchannel.ID)
}
}

Expand Down
Loading