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