File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -316,13 +316,24 @@ pub(super) fn message_request_body(
316316 message_request_body_with_tts ( content, reply_to, attachments, false )
317317}
318318
319+ /// The official client tags every message create with a snowflake nonce. Its
320+ /// absence is a self-bot signal.
321+ fn generate_nonce ( ) -> String {
322+ const DISCORD_EPOCH_MS : u64 = 1_420_070_400_000 ;
323+ let now_ms = std:: time:: SystemTime :: now ( )
324+ . duration_since ( std:: time:: UNIX_EPOCH )
325+ . map ( |elapsed| elapsed. as_millis ( ) as u64 )
326+ . unwrap_or ( DISCORD_EPOCH_MS ) ;
327+ ( now_ms. saturating_sub ( DISCORD_EPOCH_MS ) << 22 ) . to_string ( )
328+ }
329+
319330pub ( super ) fn message_request_body_with_tts (
320331 content : & str ,
321332 reply_to : Option < ReplyReference > ,
322333 attachments : & [ MessageAttachmentUpload ] ,
323334 tts : bool ,
324335) -> Value {
325- let mut body = json ! ( { "content" : content } ) ;
336+ let mut body = json ! ( { "content" : content, "nonce" : generate_nonce ( ) } ) ;
326337 if tts {
327338 body[ "tts" ] = Value :: Bool ( true ) ;
328339 }
Original file line number Diff line number Diff line change @@ -77,6 +77,13 @@ fn validates_attachment_only_message_payload() {
7777 assert_eq ! ( body[ "attachments" ] [ 0 ] [ "filename" ] , "cat.png" ) ;
7878}
7979
80+ #[ test]
81+ fn message_request_body_carries_snowflake_nonce ( ) {
82+ let body = message_request_body ( "hi" , None , & [ ] ) ;
83+ let nonce = body[ "nonce" ] . as_str ( ) . expect ( "nonce is a string" ) ;
84+ assert ! ( nonce. parse:: <u64 >( ) . is_ok( ) , "nonce must be a snowflake" ) ;
85+ }
86+
8087#[ test]
8188fn message_request_body_suppresses_reply_ping_when_disabled ( ) {
8289 let body = message_request_body (
You can’t perform that action at this time.
0 commit comments