Skip to content
Draft
Show file tree
Hide file tree
Changes from 16 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
177 changes: 176 additions & 1 deletion mm-go-irckit/service.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package irckit

import (
"encoding/binary"
"errors"
"fmt"
"math"
"regexp"
"sort"
"strconv"
"strings"
"time"
"unicode"

bolt "go.etcd.io/bbolt"

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

//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
}
Comment thread
hloeung marked this conversation as resolved.

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

// exclude direct messages
spoof := u.createSpoof(brchannel)
Comment thread
hloeung marked this conversation as resolved.
Outdated

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

// We used to stored last viewed at if present.
Comment thread
hloeung marked this conversation as resolved.
Outdated
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 {
val := binary.LittleEndian.Uint64(v)
if val > math.MaxInt64 {
logger.Errorf("timestamp value %d exceeds int64 range", val)
} else {
lastViewedAt = int64(val)
}
}
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.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
}

switch {
case p.Type == model.PostTypeAddToTeam:
nick = systemUser
ghost := u.createUserFromInfo(user)
u.Srv.Channel(brchannel.ID).Join(ghost) //nolint:errcheck
case p.Type == model.PostTypeRemoveFromTeam:
nick = systemUser
ghost := u.createUserFromInfo(user)
u.Srv.Channel(brchannel.ID).Part(ghost, "")
case p.Type == model.PostTypeJoinChannel:
ghost := u.createUserFromInfo(user)
u.Srv.Channel(brchannel.ID).Join(ghost) //nolint:errcheck
case p.Type == model.PostTypeLeaveChannel:
ghost := u.createUserFromInfo(user)
u.Srv.Channel(brchannel.ID).Part(ghost, "")
case p.Type == model.PostTypeAddToChannel:
if addedUserID, ok := props["addedUserId"].(string); ok {
ghost := u.createUserFromInfo(u.br.GetUser(addedUserID))
u.Srv.Channel(brchannel.ID).Join(ghost) //nolint:errcheck
}
case p.Type == model.PostTypeRemoveFromChannel:
if removedUserID, ok := props["removedUserId"].(string); ok {
ghost := u.createUserFromInfo(u.br.GetUser(removedUserID))
u.Srv.Channel(brchannel.ID).Part(ghost, "")
}
}

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\x02 \x1d(%s)\x1d", date, logSince))
} else {
spoof("matterircd", fmt.Sprintf("\x02Replaying msgs since %s\x02 \x1d(%s)\x1d", 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"
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:forcetypeassert,goconst
func details(u *User, toUser *User, args []string, service string) {
if service == "slack" {
Expand Down Expand Up @@ -644,9 +818,10 @@ var cmds = map[string]Command{
"logout": {handler: logout, login: true, minParams: 0, maxParams: 0},
"login": {handler: login, minParams: 2, maxParams: 5},
"part": {handler: part, login: true, minParams: 1, maxParams: 1},
"replay": {handler: replay, login: true, minParams: 1, maxParams: 2},
"scrollback": {handler: scrollback, login: true, minParams: 2, maxParams: 2},
"search": {handler: search, login: true, minParams: 1, maxParams: -1},
"searchusers": {handler: searchUsers, login: true, minParams: 1, maxParams: -1},
"scrollback": {handler: scrollback, login: true, minParams: 2, maxParams: 2},
"updatelastviewed": {handler: updatelastviewed, login: true, minParams: 1, maxParams: 1},
}

Expand Down
154 changes: 10 additions & 144 deletions mm-go-irckit/userbridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -661,174 +661,40 @@ func (u *User) createSpoof(mmchannel *bridge.ChannelInfo) func(string, string, .
}
}

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)
}
if !strings.Contains(brchannel.Name, "__") {
channelName := 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))
if brchannel.TeamID != u.br.GetMe().TeamID || u.v.GetBool(u.br.Protocol()+".prefixmainteam") {
channelName = u.br.GetTeamName(brchannel.TeamID) + "/" + brchannel.Name
}
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"
u.syncChannel(brchannel.ID, "#"+channelName)
}

// 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
}
args := []string{brchannel.Name, brchannel.TeamID}
replay(u, u, args, "")

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.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
}

switch {
case p.Type == model.PostTypeAddToTeam:
nick = systemUser
ghost := u.createUserFromInfo(user)
u.Srv.Channel(brchannel.ID).Join(ghost) //nolint:errcheck
case p.Type == model.PostTypeRemoveFromTeam:
nick = systemUser
ghost := u.createUserFromInfo(user)
u.Srv.Channel(brchannel.ID).Part(ghost, "")
case p.Type == model.PostTypeJoinChannel:
ghost := u.createUserFromInfo(user)
u.Srv.Channel(brchannel.ID).Join(ghost) //nolint:errcheck
case p.Type == model.PostTypeLeaveChannel:
ghost := u.createUserFromInfo(user)
u.Srv.Channel(brchannel.ID).Part(ghost, "")
case p.Type == model.PostTypeAddToChannel:
if addedUserID, ok := props["addedUserId"].(string); ok {
ghost := u.createUserFromInfo(u.br.GetUser(addedUserID))
u.Srv.Channel(brchannel.ID).Join(ghost) //nolint:errcheck
}
case p.Type == model.PostTypeRemoveFromChannel:
if removedUserID, ok := props["removedUserId"].(string); ok {
ghost := u.createUserFromInfo(u.br.GetUser(removedUserID))
u.Srv.Channel(brchannel.ID).Part(ghost, "")
}
}

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)
Comment thread
hloeung marked this conversation as resolved.
}
}

Expand Down
Loading