1
1
package irckit
2
2
3
3
import (
4
+ "encoding/binary"
4
5
"errors"
5
6
"fmt"
6
7
"sort"
@@ -9,6 +10,8 @@ import (
9
10
"time"
10
11
"unicode"
11
12
13
+ bolt "go.etcd.io/bbolt"
14
+
12
15
"github.com/42wim/matterircd/bridge"
13
16
"github.com/mattermost/mattermost-server/v6/model"
14
17
)
@@ -209,6 +212,177 @@ func login(u *User, toUser *User, args []string, service string) {
209
212
u .MsgUser (toUser , "login OK" )
210
213
}
211
214
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 := args [0 ]
247
+ if strings .HasPrefix (channelName , "#" ) {
248
+ channelName = channelName [1 :]
249
+ }
250
+ channelTeamID := u .br .GetMe ().TeamID
251
+ if len (args ) == 2 {
252
+ channelTeamID = args [1 ]
253
+ }
254
+ channelID := u .br .GetChannelID (channelName , channelTeamID )
255
+ brchannel , err := u .br .GetChannel (channelID )
256
+ if err != nil {
257
+ logger .Errorf ("%s not found" , channelName )
258
+ return
259
+ }
260
+
261
+ // exclude direct messages
262
+ spoof := createSpoof (u , brchannel )
263
+
264
+ since := u .br .GetLastViewedAt (brchannel .ID )
265
+ // ignore invalid/deleted/old channels
266
+ if since == 0 {
267
+ return
268
+ }
269
+
270
+ logSince := "server"
271
+ channame := brchannel .Name
272
+ if ! brchannel .DM {
273
+ channame = fmt .Sprintf ("#%s" , brchannel .Name )
274
+ }
275
+
276
+ // We used to stored last viewed at if present.
277
+ var lastViewedAt int64
278
+ key := brchannel .ID
279
+ err = u .lastViewedAtDB .View (func (tx * bolt.Tx ) error {
280
+ b := tx .Bucket ([]byte (u .User ))
281
+ if v := b .Get ([]byte (key )); v != nil {
282
+ lastViewedAt = int64 (binary .LittleEndian .Uint64 (v ))
283
+ }
284
+ return nil
285
+ })
286
+ if err != nil {
287
+ logger .Errorf ("something wrong with u.lastViewedAtDB.View for %s for channel %s (%s)" , u .Nick , channame , brchannel .ID )
288
+ lastViewedAt = since
289
+ }
290
+
291
+ // But only use the stored last viewed if it's later than what the server knows.
292
+ if lastViewedAt > since {
293
+ since = lastViewedAt + 1
294
+ logSince = "stored"
295
+ }
296
+
297
+ // post everything to the channel you haven't seen yet
298
+ postlist := u .br .GetPostsSince (brchannel .ID , since )
299
+ if postlist == nil {
300
+ // if the channel is not from the primary team id, we can't get posts
301
+ if brchannel .TeamID == u .br .GetMe ().TeamID {
302
+ logger .Errorf ("something wrong with getPostsSince for %s for channel %s (%s)" , u .Nick , channame , brchannel .ID )
303
+ }
304
+ return
305
+ }
306
+
307
+ showReplayHdr := true
308
+
309
+ mmPostList , _ := postlist .(* model.PostList )
310
+ if mmPostList == nil {
311
+ return
312
+ }
313
+ // traverse the order in reverse
314
+ for i := len (mmPostList .Order ) - 1 ; i >= 0 ; i -- {
315
+ p := mmPostList.Posts [mmPostList.Order [i ]]
316
+ if p .Type == model .PostTypeJoinLeave {
317
+ continue
318
+ }
319
+
320
+ if p .DeleteAt > p .CreateAt {
321
+ continue
322
+ }
323
+
324
+ // GetPostsSince will return older messages with reaction
325
+ // changes since LastViewedAt. This will be confusing as
326
+ // the user will think it's a duplicate, or a post out of
327
+ // order. Plus, we don't show reaction changes when
328
+ // relaying messages/logs so let's skip these.
329
+ if p .CreateAt < since {
330
+ continue
331
+ }
332
+
333
+ ts := time .Unix (0 , p .CreateAt * int64 (time .Millisecond ))
334
+
335
+ props := p .GetProps ()
336
+ botname , override := props ["override_username" ].(string )
337
+ user := u .br .GetUser (p .UserId )
338
+ nick := user .Nick
339
+ if override {
340
+ nick = botname
341
+ }
342
+
343
+ if p .Type == model .PostTypeAddToTeam || p .Type == model .PostTypeRemoveFromTeam {
344
+ nick = systemUser
345
+ }
346
+
347
+ for _ , post := range strings .Split (p .Message , "\n " ) {
348
+ if showReplayHdr {
349
+ date := ts .Format ("2006-01-02 15:04:05" )
350
+ if brchannel .DM {
351
+ spoof (nick , fmt .Sprintf ("\x02 Replaying msgs since %s\x0f (%s)" , date , logSince ))
352
+ } else {
353
+ spoof ("matterircd" , fmt .Sprintf ("\x02 Replaying msgs since %s\x0f (%s)" , date , logSince ))
354
+ }
355
+ logger .Infof ("Replaying msgs for %s for %s (%s) since %s (%s)" , u .Nick , channame , brchannel .ID , date , logSince )
356
+ showReplayHdr = false
357
+ }
358
+
359
+ if nick == systemUser {
360
+ post = "\x1d " + post + "\x1d "
361
+ }
362
+
363
+ replayMsg := fmt .Sprintf ("[%s] %s" , ts .Format ("15:04" ), post )
364
+ if (u .v .GetBool (u .br .Protocol ()+ ".prefixcontext" ) || u .v .GetBool (u .br .Protocol ()+ ".suffixcontext" )) && nick != systemUser {
365
+ threadMsgID := u .prefixContext (brchannel .ID , p .Id , p .RootId , "replay" )
366
+ replayMsg = u .formatContextMessage (ts .Format ("15:04" ), threadMsgID , post )
367
+ }
368
+ spoof (nick , replayMsg )
369
+ }
370
+
371
+ if len (p .FileIds ) == 0 {
372
+ continue
373
+ }
374
+
375
+ for _ , fname := range u .br .GetFileLinks (p .FileIds ) {
376
+ fileMsg := "\x1d download file - " + fname + "\x1d "
377
+ if u .v .GetBool (u .br .Protocol ()+ ".prefixcontext" ) || u .v .GetBool (u .br .Protocol ()+ ".suffixcontext" ) {
378
+ threadMsgID := u .prefixContext (brchannel .ID , p .Id , p .RootId , "replay_file" )
379
+ fileMsg = u .formatContextMessage (ts .Format ("15:04" ), threadMsgID , fileMsg )
380
+ }
381
+ spoof (nick , fileMsg )
382
+ }
383
+ }
384
+ }
385
+
212
386
//nolint:cyclop
213
387
func search (u * User , toUser * User , args []string , service string ) {
214
388
if service == "slack" {
@@ -472,6 +646,7 @@ var cmds = map[string]Command{
472
646
"lastsent" : {handler : lastsent , login : true , minParams : 0 , maxParams : 0 },
473
647
"logout" : {handler : logout , login : true , minParams : 0 , maxParams : 0 },
474
648
"login" : {handler : login , minParams : 2 , maxParams : 5 },
649
+ "replay" : {handler : replay , login : true , minParams : 1 , maxParams : 2 },
475
650
"search" : {handler : search , login : true , minParams : 1 , maxParams : - 1 },
476
651
"searchusers" : {handler : searchUsers , login : true , minParams : 1 , maxParams : - 1 },
477
652
"scrollback" : {handler : scrollback , login : true , minParams : 2 , maxParams : 2 },
0 commit comments