/* * 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"; type FillValue = ("status-danger" | "status-warning" | "status-positive" | "text-muted"); type Fill = [FillValue, FillValue, FillValue]; type DiffKey = keyof Diff; interface Diff { days: number, hours: number, minutes: number, seconds: number; } const HiddenVisually = findExportedComponentLazy("HiddenVisually"); export default definePlugin({ name: "MessageLatency", description: "Displays an indicator for messages that took ≥n seconds to send", authors: [Devs.arHSM], settings: definePluginSettings({ latency: { type: OptionType.NUMBER, description: "Threshold in seconds for latency indicator", default: 2 } }), patches: [ { find: "showCommunicationDisabledStyles", replacement: { match: /(message:(\i),avatar:\i,username:\(0,\i.jsxs\)\(\i.Fragment,\{children:\[)(\i&&)/, replace: "$1$self.Tooltip()({ message: $2 }),$3" } } ], stringDelta(delta: number) { const diff: Diff = { days: Math.round(delta / (60 * 60 * 24)), hours: Math.round((delta / (60 * 60)) % 24), minutes: Math.round((delta / (60)) % 60), seconds: Math.round(delta % 60), }; const str = (k: DiffKey) => diff[k] > 0 ? `${diff[k]} ${diff[k] > 1 ? k : k.substring(0, k.length - 1)}` : null; const keys = Object.keys(diff) as DiffKey[]; const ts = keys.reduce((prev, k) => { const s = str(k); return prev + ( isNonNullish(s) ? (prev !== "" ? k === "seconds" ? " and " : " " : "") + s : "" ); }, ""); return [ts || "0 seconds", diff.days === 17 && diff.hours === 1] as const; }, latencyTooltipData(message: Message) { const { id, nonce } = message; // Message wasn't received through gateway if (!isNonNullish(nonce)) return null; const delta = Math.round((SnowflakeUtils.extractTimestamp(id) - SnowflakeUtils.extractTimestamp(nonce)) / 1000); // 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; const [stringDelta, isSuspectedKotlinDiscord] = this.stringDelta(abs); const isKotlinDiscord = ahead && isSuspectedKotlinDiscord; // Also thanks dziurwa // 2 minutes const TROLL_LIMIT = 2 * 60; const { latency } = this.settings.store; const fill: Fill = isKotlinDiscord ? ["status-positive", "status-positive", "text-muted"] : delta >= TROLL_LIMIT || ahead ? ["text-muted", "text-muted", "text-muted"] : delta >= (latency * 2) ? ["status-danger", "text-muted", "text-muted"] : ["status-warning", "status-warning", "text-muted"]; return abs >= latency ? { delta: stringDelta, ahead, fill, isKotlinDiscord } : null; }, Tooltip() { return ErrorBoundary.wrap(({ message }: { message: Message; }) => { const d = this.latencyTooltipData(message); if (!isNonNullish(d)) return null; return { props => <> {} {/* Time Out indicator uses this, I think this is for a11y */} Delayed Message } ; }); }, Icon({ delta, fill, props }: { delta: string; fill: Fill, props: { onClick(): void; onMouseEnter(): void; onMouseLeave(): void; onContextMenu(): void; onFocus(): void; onBlur(): void; "aria-label"?: string; }; }) { return ; } });