/* * Vencord, a Discord client mod * Copyright (c) 2024 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ import { BaseText } from "@components/BaseText"; import { Button } from "@components/Button"; import { Flex } from "@components/Flex"; import { InfoIcon } from "@components/Icons"; import { Link } from "@components/Link"; import { copyWithToast, openUserProfile } from "@utils/discord"; import { closeAllModals, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { LazyComponent } from "@utils/react"; import { type User } from "@vencord/discord-types"; import { find, findByCode, findByCodeLazy } from "@webpack"; import { Alerts, ChannelStore, ContextMenuApi, FluxDispatcher, GuildStore, Menu, NavigationRouter, React, TabBar, TextInput, Tooltip, useMemo, useRef, useState } from "@webpack/common"; import { clearMessagesIDB, DBMessageRecord, deleteMessageIDB, deleteMessagesBulkIDB } from "../db"; import { cl, settings } from "../index"; import { LoggedMessage, LoggedMessageJSON } from "../types"; import { messageJsonToMessageClass } from "../utils"; import { importLogs } from "../utils/settingsUtils"; import { useMessages } from "./hooks"; export interface MessagePreviewProps { className: string; author: User; message: LoggedMessage; compact: boolean; isGroupStart: boolean; hideSimpleEmbedContent: boolean; childrenAccessories: any; } export interface ChildrenAccProops { channelMessageProps: { compact: boolean; channel: any; message: LoggedMessage; groupId: string; id: string; isLastItem: boolean; isHighlight: boolean; renderContentOnly: boolean; }; hasSpoilerEmbeds: boolean; isInteracting: boolean; isAutomodBlockedMessage: boolean; showClydeAiEmbeds: boolean; } const PrivateChannelRecord = findByCodeLazy(".is_message_request_timestamp,"); const MessagePreview = LazyComponent(() => find(m => m?.type?.toString().includes("previewLinkTarget:") && !m?.type?.toString().includes("HAS_THREAD"))); const ChildrenAccessories = LazyComponent(() => findByCode("channelMessageProps:{message:")); export enum LogTabs { DELETED = "Deleted", EDITED = "Edited", GHOST_PING = "Ghost Pinged" } interface Props { modalProps: ModalProps; initalQuery?: string; } export function LogsModal({ modalProps, initalQuery }: Props) { const [currentTab, setCurrentTab] = useState(LogTabs.DELETED); const [queryEh, setQuery] = useState(initalQuery ?? ""); const [sortNewest, setSortNewest] = useState(settings.store.sortNewest); const [numDisplayedMessages, setNumDisplayedMessages] = useState(settings.store.messagesToDisplayAtOnceInLogs); const contentRef = useRef(null); const { messages, total, statusTotal, pending, reset } = useMessages(queryEh, currentTab, sortNewest, numDisplayedMessages); return ( setQuery(e)} style={{ width: "100%" }} placeholder="Filter Messages" /> { setCurrentTab(e); setNumDisplayedMessages(settings.store.messagesToDisplayAtOnceInLogs); contentRef.current?.firstElementChild?.scrollTo(0, 0); }} > Deleted Edited Ghost Pinged
{ modalProps.transitionState === 1 && {messages != null && total === 0 && ( )} {!pending && messages != null && ( = settings.store.messagesToDisplayAtOnceInLogs} tab={currentTab} sortNewest={sortNewest} reset={reset} handleLoadMore={() => setNumDisplayedMessages(e => e + settings.store.messagesToDisplayAtOnceInLogs)} /> )} }
{ setSortNewest(e => { const val = !e; settings.store.sortNewest = val; return val; }); contentRef.current?.firstElementChild?.scrollTo(0, 0); }} > Sort {sortNewest ? "Oldest First" : "Newest First"}
); } interface LogContentProps { sortNewest: boolean; tab: LogTabs; visibleMessages: DBMessageRecord[]; canLoadMore: boolean; reset: () => void; handleLoadMore: () => void; } function LogsContent({ visibleMessages, canLoadMore, sortNewest, tab, reset, handleLoadMore }: LogContentProps) { if (visibleMessages.length === 0) return ; return (
{visibleMessages .map(({ message }, i) => ( ))} { canLoadMore && }
); } const LogsContentMemo = LazyComponent(() => LogsContent); function NoResults({ tab }: { tab: LogTabs; }) { const generateSuggestedTabs = (tab: LogTabs) => { switch (tab) { case LogTabs.DELETED: return { nextTab: LogTabs.EDITED, lastTab: LogTabs.GHOST_PING }; case LogTabs.EDITED: return { nextTab: LogTabs.GHOST_PING, lastTab: LogTabs.DELETED }; case LogTabs.GHOST_PING: return { nextTab: LogTabs.DELETED, lastTab: LogTabs.EDITED }; default: return { nextTab: "", lastTab: "" }; } }; const { nextTab, lastTab } = generateSuggestedTabs(tab); return (
No results in {tab}. Maybe try {nextTab} or {lastTab}
); } function EmptyLogs({ hasQuery, reset: forceUpdate }: { hasQuery: boolean; reset: () => void; }) { return (
Empty eh {!hasQuery && ( <> {({ onMouseEnter, onMouseLeave }) => (
)}
)}
); } interface LMessageProps { log: { message: LoggedMessageJSON; }; isGroupStart: boolean, reset: () => void; } function LMessage({ log, isGroupStart, reset, }: LMessageProps) { const message = useMemo(() => messageJsonToMessageClass(log), [log]); if (!message) return null; const channel = ChannelStore.getChannel(message?.channel_id); const guild = GuildStore.getGuild(channel?.guild_id); return (
{ ContextMenuApi.openContextMenu(e, () => FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" })} aria-label="Message Logger" > { NavigationRouter.transitionTo(`/channels/${ChannelStore.getChannel(message.channel_id)?.guild_id ?? "@me"}/${message.channel_id}${message.id ? "/" + message.id : ""}`); closeAllModals(); }} /> { closeAllModals(); openUserProfile(message.author.id); }} /> copyWithToast(message.content)} /> copyWithToast(message.author.id)} /> copyWithToast(message.id)} /> copyWithToast(message.channel_id)} /> { log.message.guildId != null && ( copyWithToast(log.message.guildId!)} /> ) } deleteMessageIDB(log.message.id).then(() => reset()) } /> ); }}> } /> {settings.store.ShowWhereMessageIsFrom && channel?.isDM() && message?.author && ( From {message.author.username}'s DMs )} {settings.store.ShowWhereMessageIsFrom && channel?.isGroupDM() && channel?.name && ( From {channel.name} Group DM )} {settings.store.ShowWhereMessageIsFrom && !channel?.isDM() && !channel?.isGroupDM() && channel?.name && guild?.name && ( From {channel.name} in {guild.name} )}
); } export const openLogModal = (initalQuery?: string) => openModal(modalProps => ); function isGroupStart( currentMessage: LoggedMessageJSON | undefined, previousMessage: LoggedMessageJSON | undefined, sortNewest: boolean ) { if (!currentMessage || !previousMessage) return true; if (currentMessage.id === previousMessage.id) return true; const [newestMessage, oldestMessage] = sortNewest ? [previousMessage, currentMessage] : [currentMessage, previousMessage]; if (newestMessage.author.id !== oldestMessage.author.id) return true; const timeDifferenceInMinutes = Math.abs( (new Date(newestMessage.timestamp)?.getTime() - new Date(oldestMessage.timestamp)?.getTime()) / (1000 * 60) ); return timeDifferenceInMinutes >= 5; }