12
12
BadPermission ,
13
13
PostRejected ,
14
14
PostRateLimited ,
15
+ InvalidData ,
15
16
)
16
17
17
18
import os
@@ -464,6 +465,7 @@ def get_messages_for(
464
465
self ,
465
466
user : Optional [User ],
466
467
* ,
468
+ sequence : Optional [int ] = None ,
467
469
after : Optional [int ] = None ,
468
470
before : Optional [int ] = None ,
469
471
recent : bool = False ,
@@ -476,7 +478,12 @@ def get_messages_for(
476
478
whispers meant to be displayed to moderators.
477
479
478
480
Exactly one of `after`, `begin`, `recent` or `single` must be specified:
479
- - `after=N` returns messages with ids greater than N in ascending order
481
+ - `sequence=N` returns messages that have been posted, edited, or deleted since the given
482
+ `seqno` (that is: the have seqno greater than N). Messages are returned in sequence
483
+ order.
484
+ - `after=N` returns messages with ids greater than N in ascending order. This is normally
485
+ *not* what you want for fetching messages as it omits edits and deletions; typically you
486
+ want to retrieve by seqno instead.
480
487
- `before=N` returns messages with ids less than N in descending order
481
488
- `recent=True` returns the most recent messages in descending order
482
489
- `single=123` returns a singleton list containing the single message with the given message
@@ -490,11 +497,15 @@ def get_messages_for(
490
497
mod = self .check_moderator (user )
491
498
msgs = []
492
499
493
- opt_count = sum (( after is not None , before is not None , recent , single is not None ) )
500
+ opt_count = sum (arg is not None for arg in ( sequence , after , before , single )) + bool ( recent )
494
501
if opt_count == 0 :
495
- raise RuntimeError ("Exactly one of before=, after=, recent=, or single= is required" )
502
+ raise RuntimeError (
503
+ "Exactly one of sequence=, before=, after=, recent=, or single= is required"
504
+ )
496
505
if opt_count > 1 :
497
- raise RuntimeError ("Cannot specify more than one of before=, after=, recent=, single=" )
506
+ raise RuntimeError (
507
+ "Cannot specify more than one of sequence=, before=, after=, recent=, single="
508
+ )
498
509
499
510
# Handle id mapping from an old database import in case the client is requesting
500
511
# messages since some id from the old db.
@@ -503,28 +514,45 @@ def get_messages_for(
503
514
if after <= max_old_id :
504
515
after += offset
505
516
517
+ whisper_clause = (
518
+ # For a mod we want to see:
519
+ # - all whisper_mods messsages
520
+ # - anything directed to us specifically
521
+ # - anything we sent (i.e. outbound whispers)
522
+ # - non-whispers
523
+ "whisper_mods OR whisper = :user OR user = :user OR whisper IS NULL"
524
+ if mod
525
+ # For a regular user we want to see:
526
+ # - anything with whisper_to sent to us
527
+ # - non-whispers
528
+ else "whisper = :user OR (whisper IS NULL AND NOT whisper_mods)"
529
+ if user
530
+ # Otherwise for public, non-user access we want to see:
531
+ # - non-whispers
532
+ else "whisper IS NULL AND NOT whisper_mods"
533
+ )
534
+
506
535
for row in query (
507
536
f"""
508
537
SELECT * FROM message_details
509
- WHERE room = :r AND data IS NOT NULL AND NOT filtered
538
+ WHERE room = :r AND NOT filtered { 'AND data IS NOT NULL' if sequence is None else '' }
510
539
{
540
+ 'AND seqno > :sequence' if sequence is not None else
511
541
'AND id > :after' if after is not None else
512
542
'AND id < :before' if before is not None else
513
543
'AND id = :single' if single is not None else
514
544
''
515
545
}
516
- AND (
517
- whisper IS NULL
518
- { 'OR whisper = :user' if user else '' }
519
- { 'OR whisper_mods' if mod else '' }
520
- )
546
+ AND ({ whisper_clause } )
521
547
{
522
548
'' if single is not None else
549
+ 'ORDER BY seqno ASC LIMIT :limit' if sequence is not None else
523
550
'ORDER BY id ASC LIMIT :limit' if after is not None else
524
551
'ORDER BY id DESC LIMIT :limit'
525
552
}
526
553
""" ,
527
554
r = self .id ,
555
+ sequence = sequence ,
528
556
after = after ,
529
557
before = before ,
530
558
single = single ,
@@ -590,6 +618,9 @@ def add_post(
590
618
if not self .check_write (user ):
591
619
raise BadPermission ()
592
620
621
+ if data is None or sig is None or len (sig ) != 32 :
622
+ raise InvalidData ()
623
+
593
624
whisper_mods = bool (whisper_mods )
594
625
if (whisper_to or whisper_mods ) and not self .check_moderator (user ):
595
626
app .logger .warning (f"Cannot post a whisper to { self } : { user } is not a moderator" )
@@ -655,6 +686,64 @@ def add_post(
655
686
send_mule ("message_posted" , msg ['id' ])
656
687
return msg
657
688
689
+ def edit_post (self , user : User , msg_id : int , data : bytes , sig : bytes ):
690
+ """
691
+ Edits a post in the room. The post must exist, must have been authored by the same user,
692
+ and must not be deleted. The user must *currently* have write permission (i.e. if they lose
693
+ write permission they cannot edit existing posts made before they were restricted).
694
+
695
+ Edits cannot alter the whisper_to/whisper_mods properties.
696
+
697
+ Raises:
698
+ - BadPermission() if attempting to edit another user's message or not having write
699
+ permission in the room.
700
+ - A subclass of PostRejected() if the edit is unacceptable, for instance for triggering the
701
+ profanity filter.
702
+ - NoSuchPost() if the post is deleted.
703
+ """
704
+ if not self .check_write (user ):
705
+ raise BadPermission ()
706
+
707
+ if data is None or sig is None or len (sig ) != 32 :
708
+ raise InvalidData ()
709
+
710
+ filtered = self .should_filter (user , data )
711
+ with db .transaction ():
712
+ author = query (
713
+ '''
714
+ SELECT "user" FROM messages
715
+ WHERE id = :m AND room = :r AND data IS NOT NULL
716
+ ''' ,
717
+ m = msg_id ,
718
+ r = self .id ,
719
+ ).first ()
720
+ if author is None :
721
+ raise NoSuchPost ()
722
+ author = author [0 ]
723
+ if author != user .id :
724
+ raise BadPermission ()
725
+
726
+ if filtered :
727
+ # Silent filtering is enabled and the edit failed the filter, so we want to drop the
728
+ # actual post update.
729
+ return
730
+
731
+ data_size = len (data )
732
+ unpadded_data = utils .remove_session_message_padding (data )
733
+
734
+ query (
735
+ """
736
+ UPDATE messages SET
737
+ data = :data, data_size = :data_size, signature = :sig WHERE id = :m
738
+ """ ,
739
+ m = msg_id ,
740
+ data = unpadded_data ,
741
+ data_size = data_size ,
742
+ sig = sig ,
743
+ )
744
+
745
+ send_mule ("message_edited" , msg_id )
746
+
658
747
def delete_posts (self , message_ids : List [int ], deleter : User ):
659
748
"""
660
749
Deletes the messages with the given ids. The given user performing the delete must be a
0 commit comments