2024-05-08 01:25:32 +00:00
/ *
* Vencord , a Discord client mod
* Copyright ( c ) 2024 Vendicated and contributors
* SPDX - License - Identifier : GPL - 3.0 - or - later
* /
import { definePluginSettings } from "@api/Settings" ;
import ErrorBoundary from "@components/ErrorBoundary" ;
import { Devs } from "@utils/constants" ;
import { isNonNullish } from "@utils/guards" ;
import definePlugin , { OptionType } from "@utils/types" ;
import { findExportedComponentLazy } from "@webpack" ;
import { SnowflakeUtils , Tooltip } from "@webpack/common" ;
import { Message } from "discord-types/general" ;
2024-05-11 16:09:48 +00:00
type FillValue = ( "status-danger" | "status-warning" | "status-positive" | "text-muted" ) ;
2024-05-08 01:25:32 +00:00
type Fill = [ FillValue , FillValue , FillValue ] ;
type DiffKey = keyof Diff ;
interface Diff {
days : number ,
hours : number ,
minutes : number ,
seconds : number ;
2024-05-16 02:22:45 +00:00
milliseconds : number ;
2024-05-08 01:25:32 +00:00
}
2024-05-16 02:38:36 +00:00
const DISCORD_KT_DELAY = 1471228928 ;
2024-05-08 01:25:32 +00:00
const HiddenVisually = findExportedComponentLazy ( "HiddenVisually" ) ;
export default definePlugin ( {
name : "MessageLatency" ,
description : "Displays an indicator for messages that took ≥n seconds to send" ,
authors : [ Devs . arHSM ] ,
2024-05-12 23:07:12 +00:00
2024-05-08 01:25:32 +00:00
settings : definePluginSettings ( {
latency : {
type : OptionType . NUMBER ,
description : "Threshold in seconds for latency indicator" ,
default : 2
2024-05-12 23:07:12 +00:00
} ,
detectDiscordKotlin : {
type : OptionType . BOOLEAN ,
description : "Detect old Discord Android clients" ,
default : true
2024-05-16 02:22:45 +00:00
} ,
showMillis : {
type : OptionType . BOOLEAN ,
description : "Show milliseconds" ,
default : false
2024-05-08 01:25:32 +00:00
}
} ) ,
2024-05-12 23:07:12 +00:00
2024-05-08 01:25:32 +00:00
patches : [
{
find : "showCommunicationDisabledStyles" ,
replacement : {
match : /(message:(\i),avatar:\i,username:\(0,\i.jsxs\)\(\i.Fragment,\{children:\[)(\i&&)/ ,
replace : "$1$self.Tooltip()({ message: $2 }),$3"
}
}
] ,
2024-05-12 23:07:12 +00:00
2024-05-16 02:22:45 +00:00
stringDelta ( delta : number , showMillis : boolean ) {
2024-05-08 01:25:32 +00:00
const diff : Diff = {
2024-05-16 02:22:45 +00:00
days : Math.round ( delta / ( 60 * 60 * 24 * 1000 ) ) ,
hours : Math.round ( ( delta / ( 60 * 60 * 1000 ) ) % 24 ) ,
minutes : Math.round ( ( delta / ( 60 * 1000 ) ) % 60 ) ,
seconds : Math.round ( delta / 1000 % 60 ) ,
milliseconds : Math.round ( delta % 1000 )
2024-05-08 01:25:32 +00:00
} ;
2024-05-11 16:09:48 +00:00
const str = ( k : DiffKey ) = > diff [ k ] > 0 ? ` ${ diff [ k ] } ${ diff [ k ] > 1 ? k : k.substring ( 0 , k . length - 1 ) } ` : null ;
2024-05-08 01:25:32 +00:00
const keys = Object . keys ( diff ) as DiffKey [ ] ;
2024-05-11 16:09:48 +00:00
const ts = keys . reduce ( ( prev , k ) = > {
const s = str ( k ) ;
return prev + (
isNonNullish ( s )
? ( prev !== ""
2024-05-16 02:22:45 +00:00
? ( showMillis ? k === "milliseconds" : k === "seconds" )
2024-05-11 16:09:48 +00:00
? " and "
: " "
: "" ) + s
: ""
) ;
} , "" ) ;
2024-05-12 23:07:12 +00:00
return ts || "0 seconds" ;
2024-05-08 01:25:32 +00:00
} ,
2024-05-12 23:07:12 +00:00
2024-05-08 01:25:32 +00:00
latencyTooltipData ( message : Message ) {
2024-05-16 02:22:45 +00:00
const { latency , detectDiscordKotlin , showMillis } = this . settings . store ;
2024-05-08 01:25:32 +00:00
const { id , nonce } = message ;
// Message wasn't received through gateway
if ( ! isNonNullish ( nonce ) ) return null ;
2024-05-26 16:24:02 +00:00
// Bots basically never send a nonce, and if someone does do it then it's usually not a snowflake
if ( message . bot ) return null ;
2024-05-12 23:07:12 +00:00
let isDiscordKotlin = false ;
2024-05-16 02:22:45 +00:00
let delta = SnowflakeUtils . extractTimestamp ( id ) - SnowflakeUtils . extractTimestamp ( nonce ) ; // milliseconds
if ( ! showMillis ) {
delta = Math . round ( delta / 1000 ) * 1000 ;
}
2024-05-12 23:07:12 +00:00
// Old Discord Android clients have a delay of around 17 days
// This is a workaround for that
2024-05-16 02:22:45 +00:00
if ( - delta >= DISCORD_KT_DELAY - 86400000 ) { // One day of padding for good measure
2024-05-12 23:07:12 +00:00
isDiscordKotlin = detectDiscordKotlin ;
delta += DISCORD_KT_DELAY ;
}
2024-05-08 01:25:32 +00:00
// Thanks dziurwa (I hate you)
// This is when the user's clock is ahead
// Can't do anything if the clock is behind
const abs = Math . abs ( delta ) ;
const ahead = abs !== delta ;
2024-05-16 02:38:36 +00:00
const latencyMillis = latency * 1000 ;
2024-05-08 01:25:32 +00:00
2024-05-16 02:38:36 +00:00
const stringDelta = abs >= latencyMillis ? this . stringDelta ( abs , showMillis ) : null ;
2024-05-08 01:25:32 +00:00
// Also thanks dziurwa
// 2 minutes
2024-05-16 02:22:45 +00:00
const TROLL_LIMIT = 2 * 60 * 1000 ;
2024-05-08 01:25:32 +00:00
2024-05-12 23:07:12 +00:00
const fill : Fill = isDiscordKotlin
2024-05-11 16:09:48 +00:00
? [ "status-positive" , "status-positive" , "text-muted" ]
: delta >= TROLL_LIMIT || ahead
? [ "text-muted" , "text-muted" , "text-muted" ]
2024-05-16 02:38:36 +00:00
: delta >= ( latencyMillis * 2 )
2024-05-11 16:09:48 +00:00
? [ "status-danger" , "text-muted" , "text-muted" ]
: [ "status-warning" , "status-warning" , "text-muted" ] ;
2024-05-08 01:25:32 +00:00
2024-05-16 02:38:36 +00:00
return ( abs >= latencyMillis || isDiscordKotlin ) ? { delta : stringDelta , ahead , fill , isDiscordKotlin } : null ;
2024-05-08 01:25:32 +00:00
} ,
2024-05-12 23:07:12 +00:00
2024-05-08 01:25:32 +00:00
Tooltip() {
return ErrorBoundary . wrap ( ( { message } : { message : Message ; } ) = > {
const d = this . latencyTooltipData ( message ) ;
if ( ! isNonNullish ( d ) ) return null ;
2024-05-12 23:07:12 +00:00
let text : string ;
if ( ! d . delta ) {
text = "User is suspected to be on an old Discord Android client" ;
} else {
text = ( d . ahead ? ` This user's clock is ${ d . delta } ahead. ` : ` This message was sent with a delay of ${ d . delta } . ` ) + ( d . isDiscordKotlin ? " User is suspected to be on an old Discord Android client." : "" ) ;
}
2024-05-08 01:25:32 +00:00
return < Tooltip
2024-05-12 23:07:12 +00:00
text = { text }
2024-05-08 01:25:32 +00:00
position = "top"
>
{
props = > < >
{ < this.Icon delta = { d . delta } fill = { d . fill } props = { props } / > }
{ /* Time Out indicator uses this, I think this is for a11y */ }
< HiddenVisually > Delayed Message < / HiddenVisually >
< / >
}
< / Tooltip > ;
} ) ;
} ,
2024-05-12 23:07:12 +00:00
2024-05-08 01:25:32 +00:00
Icon ( { delta , fill , props } : {
2024-05-12 23:07:12 +00:00
delta : string | null ;
2024-05-08 01:25:32 +00:00
fill : Fill ,
props : {
onClick ( ) : void ;
onMouseEnter ( ) : void ;
onMouseLeave ( ) : void ;
onContextMenu ( ) : void ;
onFocus ( ) : void ;
onBlur ( ) : void ;
"aria-label" ? : string ;
} ;
} ) {
return < svg
xmlns = "http://www.w3.org/2000/svg"
viewBox = "0 0 16 16"
width = "12"
height = "12"
role = "img"
fill = "none"
style = { { marginRight : "8px" , verticalAlign : - 1 } }
2024-05-12 23:07:12 +00:00
aria - label = { delta ? ? "Old Discord Android client" }
2024-05-08 01:25:32 +00:00
aria - hidden = "false"
{ . . . props }
>
< path
fill = { ` var(-- ${ fill [ 0 ] } ) ` }
d = "M4.8001 12C4.8001 11.5576 4.51344 11.2 4.16023 11.2H2.23997C1.88676 11.2 1.6001 11.5576 1.6001 12V13.6C1.6001 14.0424 1.88676 14.4 2.23997 14.4H4.15959C4.5128 14.4 4.79946 14.0424 4.79946 13.6L4.8001 12Z"
/ >
< path
fill = { ` var(-- ${ fill [ 1 ] } ) ` }
d = "M9.6001 7.12724C9.6001 6.72504 9.31337 6.39998 8.9601 6.39998H7.0401C6.68684 6.39998 6.40011 6.72504 6.40011 7.12724V13.6727C6.40011 14.0749 6.68684 14.4 7.0401 14.4H8.9601C9.31337 14.4 9.6001 14.0749 9.6001 13.6727V7.12724Z"
/ >
< path
fill = { ` var(-- ${ fill [ 2 ] } ) ` }
d = "M14.4001 2.31109C14.4001 1.91784 14.1134 1.59998 13.7601 1.59998H11.8401C11.4868 1.59998 11.2001 1.91784 11.2001 2.31109V13.6888C11.2001 14.0821 11.4868 14.4 11.8401 14.4H13.7601C14.1134 14.4 14.4001 14.0821 14.4001 13.6888V2.31109Z"
/ >
< / svg > ;
}
} ) ;