11package irckit
22
33import (
4+ "encoding/binary"
45 "errors"
56 "fmt"
67 "sort"
@@ -9,6 +10,8 @@ import (
910 "time"
1011 "unicode"
1112
13+ bolt "go.etcd.io/bbolt"
14+
1215 "github.com/42wim/matterircd/bridge"
1316 "github.com/mattermost/mattermost-server/v6/model"
1417)
@@ -209,6 +212,174 @@ func login(u *User, toUser *User, args []string, service string) {
209212 u .MsgUser (toUser , "login OK" )
210213}
211214
215+ func createSpoof (u * User , mmchannel * bridge.ChannelInfo ) func (string , string , ... int ) {
216+ if strings .Contains (mmchannel .Name , "__" ) {
217+ return func (nick string , msg string , maxlen ... int ) {
218+ if usr , ok := u .Srv .HasUser (nick ); ok {
219+ u .MsgSpoofUser (usr , u .Nick , msg )
220+ } else {
221+ logger .Errorf ("%s not found for replay msg" , nick )
222+ }
223+ }
224+ }
225+
226+ channelName := mmchannel .Name
227+
228+ if mmchannel .TeamID != u .br .GetMe ().TeamID || u .v .GetBool (u .br .Protocol ()+ ".prefixmainteam" ) {
229+ channelName = u .br .GetTeamName (mmchannel .TeamID ) + "/" + mmchannel .Name
230+ }
231+
232+ u .syncChannel (mmchannel .ID , "#" + channelName )
233+ ch := u .Srv .Channel (mmchannel .ID )
234+
235+ return ch .SpoofMessage
236+ }
237+
238+ //nolint:funlen,gocognit,gocyclo,cyclop
239+ func replay (u * User , toUser * User , args []string , service string ) {
240+ if len (args ) == 0 || len (args ) > 2 {
241+ u .MsgUser (toUser , "need REPLAY (#<channel>|<user>)" )
242+ u .MsgUser (toUser , "e.g. REPLAY #bugs" )
243+ return
244+ }
245+
246+ channelName := strings .TrimPrefix (args [0 ], "#" )
247+ channelTeamID := u .br .GetMe ().TeamID
248+ if len (args ) == 2 {
249+ channelTeamID = args [1 ]
250+ }
251+ channelID := u .br .GetChannelID (channelName , channelTeamID )
252+ brchannel , err := u .br .GetChannel (channelID )
253+ if err != nil {
254+ logger .Errorf ("%s not found" , channelName )
255+ return
256+ }
257+
258+ // exclude direct messages
259+ spoof := createSpoof (u , brchannel )
260+
261+ since := u .br .GetLastViewedAt (brchannel .ID )
262+ // ignore invalid/deleted/old channels
263+ if since == 0 {
264+ return
265+ }
266+
267+ logSince := "server"
268+ channame := brchannel .Name
269+ if ! brchannel .DM {
270+ channame = fmt .Sprintf ("#%s" , brchannel .Name )
271+ }
272+
273+ // We used to stored last viewed at if present.
274+ var lastViewedAt int64
275+ key := brchannel .ID
276+ err = u .lastViewedAtDB .View (func (tx * bolt.Tx ) error {
277+ b := tx .Bucket ([]byte (u .User ))
278+ if v := b .Get ([]byte (key )); v != nil {
279+ lastViewedAt = int64 (binary .LittleEndian .Uint64 (v ))
280+ }
281+ return nil
282+ })
283+ if err != nil {
284+ logger .Errorf ("something wrong with u.lastViewedAtDB.View for %s for channel %s (%s)" , u .Nick , channame , brchannel .ID )
285+ lastViewedAt = since
286+ }
287+
288+ // But only use the stored last viewed if it's later than what the server knows.
289+ if lastViewedAt > since {
290+ since = lastViewedAt + 1
291+ logSince = "stored"
292+ }
293+
294+ // post everything to the channel you haven't seen yet
295+ postlist := u .br .GetPostsSince (brchannel .ID , since )
296+ if postlist == nil {
297+ // if the channel is not from the primary team id, we can't get posts
298+ if brchannel .TeamID == u .br .GetMe ().TeamID {
299+ logger .Errorf ("something wrong with getPostsSince for %s for channel %s (%s)" , u .Nick , channame , brchannel .ID )
300+ }
301+ return
302+ }
303+
304+ showReplayHdr := true
305+
306+ mmPostList , _ := postlist .(* model.PostList )
307+ if mmPostList == nil {
308+ return
309+ }
310+ // traverse the order in reverse
311+ for i := len (mmPostList .Order ) - 1 ; i >= 0 ; i -- {
312+ p := mmPostList.Posts [mmPostList.Order [i ]]
313+ if p .Type == model .PostTypeJoinLeave {
314+ continue
315+ }
316+
317+ if p .DeleteAt > p .CreateAt {
318+ continue
319+ }
320+
321+ // GetPostsSince will return older messages with reaction
322+ // changes since LastViewedAt. This will be confusing as
323+ // the user will think it's a duplicate, or a post out of
324+ // order. Plus, we don't show reaction changes when
325+ // relaying messages/logs so let's skip these.
326+ if p .CreateAt < since {
327+ continue
328+ }
329+
330+ ts := time .Unix (0 , p .CreateAt * int64 (time .Millisecond ))
331+
332+ props := p .GetProps ()
333+ botname , override := props ["override_username" ].(string )
334+ user := u .br .GetUser (p .UserId )
335+ nick := user .Nick
336+ if override {
337+ nick = botname
338+ }
339+
340+ if p .Type == model .PostTypeAddToTeam || p .Type == model .PostTypeRemoveFromTeam {
341+ nick = systemUser
342+ }
343+
344+ for _ , post := range strings .Split (p .Message , "\n " ) {
345+ if showReplayHdr {
346+ date := ts .Format ("2006-01-02 15:04:05" )
347+ if brchannel .DM {
348+ spoof (nick , fmt .Sprintf ("\x02 Replaying msgs since %s\x0f (%s)" , date , logSince ))
349+ } else {
350+ spoof ("matterircd" , fmt .Sprintf ("\x02 Replaying msgs since %s\x0f (%s)" , date , logSince ))
351+ }
352+ logger .Infof ("Replaying msgs for %s for %s (%s) since %s (%s)" , u .Nick , channame , brchannel .ID , date , logSince )
353+ showReplayHdr = false
354+ }
355+
356+ if nick == systemUser {
357+ post = "\x1d " + post + "\x1d "
358+ }
359+
360+ replayMsg := fmt .Sprintf ("[%s] %s" , ts .Format ("15:04" ), post )
361+ if (u .v .GetBool (u .br .Protocol ()+ ".prefixcontext" ) || u .v .GetBool (u .br .Protocol ()+ ".suffixcontext" )) && nick != systemUser {
362+ threadMsgID := u .prefixContext (brchannel .ID , p .Id , p .RootId , "replay" )
363+ replayMsg = u .formatContextMessage (ts .Format ("15:04" ), threadMsgID , post )
364+ }
365+ spoof (nick , replayMsg )
366+ }
367+
368+ if len (p .FileIds ) == 0 {
369+ continue
370+ }
371+
372+ for _ , fname := range u .br .GetFileLinks (p .FileIds ) {
373+ fileMsg := "\x1d download file - " + fname + "\x1d "
374+ if u .v .GetBool (u .br .Protocol ()+ ".prefixcontext" ) || u .v .GetBool (u .br .Protocol ()+ ".suffixcontext" ) {
375+ threadMsgID := u .prefixContext (brchannel .ID , p .Id , p .RootId , "replay_file" )
376+ fileMsg = u .formatContextMessage (ts .Format ("15:04" ), threadMsgID , fileMsg )
377+ }
378+ spoof (nick , fileMsg )
379+ }
380+ }
381+ }
382+
212383//nolint:cyclop
213384func search (u * User , toUser * User , args []string , service string ) {
214385 if service == "slack" {
@@ -472,6 +643,7 @@ var cmds = map[string]Command{
472643 "lastsent" : {handler : lastsent , login : true , minParams : 0 , maxParams : 0 },
473644 "logout" : {handler : logout , login : true , minParams : 0 , maxParams : 0 },
474645 "login" : {handler : login , minParams : 2 , maxParams : 5 },
646+ "replay" : {handler : replay , login : true , minParams : 1 , maxParams : 2 },
475647 "search" : {handler : search , login : true , minParams : 1 , maxParams : - 1 },
476648 "searchusers" : {handler : searchUsers , login : true , minParams : 1 , maxParams : - 1 },
477649 "scrollback" : {handler : scrollback , login : true , minParams : 2 , maxParams : 2 },
0 commit comments